Skip to content

Proposed publisher API, generated from the consumption specification - #147

Open
0x73746F66 wants to merge 19 commits into
CycloneDX:mainfrom
0x73746F66:main
Open

Proposed publisher API, generated from the consumption specification#147
0x73746F66 wants to merge 19 commits into
CycloneDX:mainfrom
0x73746F66:main

Conversation

@0x73746F66

@0x73746F66 0x73746F66 commented May 10, 2025

Copy link
Copy Markdown

Proposes a publisher API for TEA: how software gets into a transparency log. The
consumption specification already says how it comes out.

spec/publisher/openapi.yaml is generated. It is spec/openapi.yaml plus the
publication operations in spec/publisher/overlay.yaml, merged by
spec/publisher/build.mjs. Anything the two share is a $ref into the
consumption definitions, and the build fails if the overlay redefines something
the consumption specification already owns. The publisher version is the
consumer version by construction.

The earlier draft was a hand-maintained copy and had drifted to 0.0.3 schemas
under parallel names: artifactFormat beside artifact-format, typeUuid
beside uuid. Generating the document removes that possibility instead of
correcting the symptom. Reasoning for each decision is in
spec/publisher/README.md.

What the overlay adds

  • Create, update and delete for products, components and releases, split into
    productRelease and componentRelease to match the consumption model.
  • Collections published through their release, PUT /productRelease/{uuid}/collection, because the consumption specification says
    a collection's UUID is its release's and an update only bumps the version.
    updateReason is required.
  • Artifacts registered first, bytes uploaded per format afterwards. An SBOM is
    often tens of megabytes, and a publisher who has to resend all of it to fix
    one metadata field will not fix the field. Uploads verify Content-Digest
    (RFC 9530).
  • Signatures that record the scheme, algorithm, key id, certificate, chain and
    transparency-log entry. The consumption API exposes only signatureUrl, so a
    consumer currently has to work out the scheme from the bytes before it can
    verify anything.
  • Distributions, which say how a release is obtained as distinct from what is
    known about it. They hang off a component release, which is where
    release.distributions is defined.
  • An access policy of private, shared (named organisations, optional expiry)
    or public, inherited from artifact to collection to release to product.
  • GET /publications, reporting what an organisation has published and who can
    read it.
  • publishTo, mirroring to targets registered at /publicationTargets.
  • Idempotency-Key on creates, because pipelines retry.

Two rules in the access policy do most of the work. Inheritance narrows and
never widens, so a server refuses an attempt to loosen a child with
ACCESS_WIDENS_PARENT; without that, marking a product private would guarantee
nothing, because any artifact beneath it could be made public on its own. And a
change to public requires confirm=public, because no later request recalls
what has already been fetched.

None of it asks anything new of a consumer. A reader denied by a policy gets the
existing OBJECT_NOT_SHAREABLE.

One change to the consumption specification

spec/openapi.yaml gains an empty alternative in its global security, making
authentication optional. This is the only edit to a file the working group owns,
and the only part of this PR that needs a decision rather than a review.

Without it the publication API cannot mean what it says. visibility: public is
defined as readable without authentication, and the discovery sequence (take the
domain from a TEI, fetch that host's discovery document, call the root it names)
has no step at which a credential could be obtained. A specification that
requires one everywhere closes its own entry point, and a server that does serve
a public object anonymously is non-conformant for it.

The change only widens what is allowed, so nothing conformant breaks. A server
may still refuse every anonymous request. Every operation in the consumption
document is a read, so nothing becomes writable. In exchange, build.mjs gives
each of the 24 write operations an explicit bearerAuth/basicAuth
requirement, so anonymous writes stay non-conformant.

If the working group would rather leave the document alone, the alternative is
prose saying servers may relax it for public objects, and accepting that the
document and the behaviour disagree.

Compatibility with 0.4.0

The consumption specification is otherwise untouched. diff between this branch
and the copy that implementation validates against reports the security block
above and nothing else.

redocly lint reports 24 errors on the generated document and the same 24 on
spec/openapi.yaml alone: 23 missing operation summaries and one nullable in
pagination-details. CI compares the two counts and fails if the publisher
document is ever worse than the specification it is built from.

Checked against a deployed implementation, Vulnetix Resolve.

23 of 23 consumption operations exercised, 160 test cases, 0 failures, 85
responses validated against the upstream schemas and all conforming. The
Insights API adds 2 operations and 27 cases, with 16 responses validated against
CycloneDX 1.6.

All four access modes work end to end. An anonymous consumer resolves a TEI at
the root advertised in /.well-known/tea, then reads a public release, its
collection, its artifacts and their bytes with no credential at any step.
shared grants name organisation UUIDs with an optional expiry, and an expired
grant is not a grant. private is the default and owner-only, with GET /publications as the way an owner confirms it. Discovery resolves both
urn:tei:uuid: and urn:tei:purl:, listings filter on idType and idValue,
and retrieval walks release to collection to artifact to content. A refusal
answers OBJECT_UNKNOWN and not 403, so nobody can enumerate another
publisher's UUIDs.

Those 160 cases cover the consumption operations. The publication operations are
implemented and in use, and the implementation now checks itself against this
document: every error code it emits must be a value publisher-error-type
declares, and every operation this document declares must be routed. They are
not yet driven by a conformance suite. That is the next piece of work, not a
claim being made here.

What implementing it found

Five things this document could not express, all fixed in it now.

A product release could not state its components, which the consumption
productRelease lists as required. An artifact could not set distributionIds.

public asked for a separate confirmation without saying what one was, so our
implementation uses ?confirm=public. And publishTo named targets that
nothing could configure, with failure semantics the README itself admitted were
undefined.

Open questions

Artifact versions. The consumer API addresses artifacts as
/artifact/{uuid}/{artifactVersion}, but create and update do not say how a
publisher advances that version.

Mirror authentication runs one way only. Nothing says how a receiving server
decides whether to accept a mirrored object, or how it records that the object
arrived as a copy. Consensus across transparency logs requires this to be resolved, or consensus is not possible and there is nothing trustworthy across transparency exchange servers.

Signed-off-by: Chris Langton <chris@vulnetix.com>
Signed-off-by: Chris Langton <chris@vulnetix.com>
- Add authentication schemes
- Create discovery endpoints (/.well-known/tea/{id})
- Standardise property naming between consumer/publisher APIs
- Implement advanced filtering for components and artifacts
- Add lifecycle status endpoints

Signed-off-by: Chris Langton <chris@vulnetix.com>
@0x73746F66
0x73746F66 marked this pull request as ready for review May 10, 2025 13:16
@0x73746F66
0x73746F66 requested review from madpah and oej as code owners May 10, 2025 13:16
Comment thread spec/publisher/openapi.yaml Outdated
Comment thread spec/publisher/openapi.yaml Outdated
@ppkarwasz

Copy link
Copy Markdown
Contributor

@0x73746F66,

Thank you for updating the Publisher spec! 💯 It was certainly way of out of date!

However, I notice that the proposed spec has also a lot of subtle differences (schema names, parameter names, schema contents) from the current Consume API. Should we mark this PR as draft and work on it after the hackaton?

@0x73746F66
0x73746F66 marked this pull request as draft May 21, 2025 10:43
Signed-off-by: Chris Langton <chris@vulnetix.com>
Signed-off-by: Chris Langton <chris@vulnetix.com>
Signed-off-by: Chris Langton <chris@vulnetix.com>
Signed-off-by: Chris Langton <chris@vulnetix.com>

@taleodor taleodor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the Pull Request! Did first read through for the document, there may be more things to discuss later, but raised issues in the comments that require further work and / or discussion.

Comment thread spec/publisher/openapi.yaml
Comment thread spec/publisher/openapi.yaml Outdated
Comment thread spec/publisher/openapi.yaml Outdated
post:
description: Create TEA Product entry for the supplied product identifier
operationId: createTeaProduct
requestBody:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, most of those properties are not currently part of the Consumer API spec. Additionally, it would be helpful to create a shared Schema object as we did throughout the Consumer API.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, refer to PURL in the consumer API

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, don't see how this is relevant to PURL at all.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're going to have to be clear then, because of you're referring to the markdown spec and find these then perhaps the consumer spec should be updated after beta1 to align like this has

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my previous comment, those are listed in markdown as possible identifiers, we shouldn't have a separate field for each type and instead expand TeaIdentifier. We may also want to be a little stricter about identifier types - it's possible that markdown needs to be updated here as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The consumer API returns the constructed PURL as a dynamic response property

To be able to do that, the publisher needs to gather data elements and store them to construct the PURL

A string that is a full PURL can be described by th publisher as a loaded optional string, where some values are duplicated every API call

Or an API spec that implements the intent of returning a partial PURL could do so wth partial PURL support by not including some of the additional elements I added

I believe reduction of duplicates, and addition of fields available in a PURL, is the best way to implement the spec.
If we do anything lese it is either adding duplication, and it is putting the responcibility onto publishers to know how to construct a PURL, and being optional this will probabl be skipped...

If we want to do ourselves a favour later when we implement the PURL support for searching, we should be forward thinking now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The consumer API returns the constructed PURL as a dynamic response property

A single entity may have several PURLs - that is the state of the PURL spec at the moment, I specifically brought this question on the last PURL community meeting. Having consistent and unique PURLs is a noble goal, but I don't think that is achievable.

As a basic example, imagine a Docker image that also has code hosted on GitHub. You may have PURL pkg:github/... and pkg:docker/... for the same thing where both are valid. That is also part of the reason why separate TEI is needed.

Therefore, I believe we should actually leave it to publishers to define their identifiers.

Finally, PURL is very domain (type) specific and things like sku and barcode are not even basic PURL elements, so not sure why they are here in this context.

Comment thread spec/publisher/openapi.yaml Outdated
$ref: '#/components/responses/404-object-by-id-not-found'
tags:
- TEA Component
/component/{componentIdentifier}:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest using same name for {uuid} across the spec and refer to the Schema uuid definition as in the Consumer API.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that makes sense is we merge them and the meaning has no distinctions from publisher and consumer. i.e. a publisher does not have the primary key yet (until the database record is inserted) so it cannot be the same entity as the resultant response entity in the consumer API spec.

It is common for request models to be distinct from response models in all API designs - this is why I deveated deliberately

Comment thread spec/publisher/openapi.yaml Outdated
Comment thread spec/publisher/openapi.yaml Outdated
Comment thread spec/publisher/openapi.yaml Outdated
Comment thread spec/publisher/openapi.yaml
Comment thread spec/publisher/openapi.yaml Outdated
$ref: '#/components/operations/standardDelete'
tags:
- TEA Release
/collection:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should discuss whether explicit CRUD on Collections should be allowed, see #152

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of the referenced issue has a mixture of obviously good ideas but I believe that dogma might be winning over critical thinking a little here. Without question why these views are better than allowing publishers the flexibilities is problematic, we automatically impose an arbitrary constraint. Perhaps constraints should be sparingly applied to a stnadard like TEA

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me this is actually purely implementation prospective. I think we should add calls related to adding artifacts, then it becomes more clear that having both CRUD on collections and on artifacts conflicts with each other logically. For now, since artifact CRUD is not there it may be less visible.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRUD is a por description for this spec, given R for read is not apparent and therefore chaining requests that rely on prior call results with new unknown UUIDs can be a logical constraint which only a read can resolve..

Better to reduce chaining requests for creation, and offer incremental limited updater methods expected to be called at much later dates, dependant on having a timely GET request for freshness

Would you agree a single POST with everything is both concise, organised, and performant? With consideration any complications that can be addressed with a GET + PATCH chain should be back ported to the POST to maintain these desirable characteristics and avoid dependency on complicated chaining logic?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me, but right now we have collection type update enum, which has things like Vex Updated, Artifact Updated, Artifact Removed, Artifact Added. If you do batch requests where you both add and update artifacts, this concept becomes murky.

Another issue with batch requests is that it eventually would lead to conversation about collection own lifecycle - which to me is more bureaucracy without much gain. I prefer a system where I upload an artifact and it becomes visible right away, instead of a system where I need to first upload an artifact (or a batch) and then approve a new collection on top of that. This is a point to discuss though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not only "approve" - it's in many cases "sing the collection" too. It's like a commit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a conversation on this.

My current implementation idea is that signing should happen automatically on the backend - we can add constraints that artifacts should be signed, etc.

In SaaS scenarios, we can easily have dozens of releases per day for a single component and there can be many components. Each of those releases can have multiple collection versions. I cannot imagine a human signing all of them.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keyless signing (Cosign signature) is definitely a superior choice, however we'll need to support signing with Tuff, CMK, and PGP

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cosign as a service is not a superior choice for a lot of reasons. The software is cool though. Signing is discussed elsewhere and we need to open that discussion.

Comment thread spec/publisher/openapi.yaml Outdated
Signed-off-by: Chris Langton <chris@vulnetix.com>
@0x73746F66

Copy link
Copy Markdown
Author

Awesome feedback @taleodor ty. I have some cnversations resolved and others with clarifications t odiscuss

Signed-off-by: Chris Langton <chris@vulnetix.com>
…release paths

Signed-off-by: Chris Langton <chris@vulnetix.com>
@oej

oej commented Dec 18, 2025

Copy link
Copy Markdown
Collaborator

Just pinging to see if this still is an active PR that we need to merge as the basis of future work on the publisher API or if it's superseded by other PRs already merged.

@oej

oej commented Dec 18, 2025

Copy link
Copy Markdown
Collaborator

There are conflicts that needs to be handled.

@0x73746F66

0x73746F66 commented Aug 1, 2026

Copy link
Copy Markdown
Author

Update PR description, and rebased main. Publisher spec is not generated, so consumer updates are shared with publisher spec going forward

…s and distribution

The document did not validate as OpenAPI 3.1, and the way it failed
mattered. `components.operations` is not a field OpenAPI defines, so the
three $refs pointing into it never resolved and every delete operation
declared no responses at all — a generator reading this produced deletes
that returned nothing. Redocly reported 9 errors; it now reports 0.

Error responses carried `application/json: {}`, an empty schema that says
nothing about what comes back. Publication runs in release pipelines, so
failures need to be actionable without a human reading prose: there is
now an `error` schema with a machine-readable code.

Three things were missing rather than broken.

Artifacts. The draft could create products, components, releases and
collections but had no way to publish the artifact that carries the SBOM,
VEX or attestation the exchange exists to move. Creating an artifact and
uploading its bytes are separate operations, because an SBOM is routinely
tens of megabytes and a publisher who must resend all of it to fix one
metadata field will not fix the field. Content goes up per format in its
own media type rather than base64 in JSON, stored bytes are immutable so
a recorded checksum cannot start describing different content, and
Content-Digest is verified so a truncated retry fails loudly instead of
publishing corruption under a checksum that claims otherwise.

Signatures. The consumption API exposes only a signatureUrl, leaving a
consumer to infer the scheme from the bytes. The publisher knows it, so
artifactSignature records the scheme, key id, certificate chain and
transparency-log entry — which is what makes a signature checkable rather
than merely present.

Distribution. TEA describes how data is fetched, not who may fetch it.
That boundary works for a consumption API and cannot hold for a
publication one, where the same server holds public material, material
shared with named counterparties, and internal material. Three
visibilities, plus mirroring to other TEA servers. Inheritance narrows
and never widens — a child may restrict what it inherits and a server
rejects any attempt to loosen it, because otherwise marking a product
private would guarantee nothing when any artifact beneath it could be
made public. GET /distribution/{uuid} reports declared against effective
and names the ancestor responsible, since the gap between those two is
where accidental disclosure hides.

Idempotency-Key on creates, because pipelines retry and a timeout that
actually succeeded otherwise yields a duplicate found by a consumer
rather than by the publisher.

The README records what changed and three open questions: releases are
not split into product and component as the consumption API splits them,
there is no publisher-side read surface, and publishTo does not say
whether a failed mirror blocks local publication.

Signed-off-by: Christopher Langton <chris@vulnetix.com>
# Conflicts:
#	contributors.md
#	spec/publisher/openapi.json
…d the drift

The publisher document was a hand-maintained copy of the consumption
specification and had fallen four minor versions behind it. The
consumption API is at 0.4.0; the copy still carried schemas from 0.0.3,
renamed along the way — artifactFormat beside the real artifact-format,
typeUuid beside uuid, a private artifactChecksum beside checksum. A
combined document therefore held two definitions of the same concept,
each free to move independently, with nothing to say which one a
publisher should believe.

It is now generated. build.mjs merges spec/openapi.yaml with a
publication overlay, so everything shared is a $ref into the consumption
specification's own definitions and the publisher version is copied from
the consumer rather than chosen. The build refuses to run if the overlay
declares a schema, parameter, response, request body or path method the
consumption specification already owns — the overlay may only add.

Alignment corrections this surfaced:

- Releases are split into /productRelease and /componentRelease. The
  consumption API has distinguished the two since 0.1.0 and a single
  /release could not say which it was creating.
- Collections are addressed through their release. The consumption
  specification states a collection's UUID matches its release and that
  an update only changes the version, so it is not an independently
  created object: PUT /productRelease/{uuid}/collection, not
  POST /collection.
- Errors reuse the consumption envelope. publisher-error-response has the
  same { error } shape as error-response over an enumeration that repeats
  OBJECT_UNKNOWN and OBJECT_NOT_SHAREABLE and adds the failures only a
  writer can hit, so clients branch on one enumeration rather than two.
- distribution became access-policy, because the consumption
  specification already uses release-distribution for a downloadable
  build with its own URL and checksums. Reusing the word for access
  control would have been actively misleading.

Enforcement of the access policy needs no new consumer behaviour: a
denied consumer gets OBJECT_NOT_SHAREABLE, which already exists in
unknown-error-type.

The generated document now adds no lint errors of its own. It reports 24,
and linting spec/openapi.yaml alone reports the same 24 — 23 operations
missing summaries and one OpenAPI 3.0 `nullable` on
pagination-details.nextPageToken. Those belong upstream rather than
patched here, where they would put the fork at odds with a file upstream
owns. CI compares the two counts and fails only if the overlay makes
things worse.

Signed-off-by: Christopher Langton <chris@vulnetix.com>
createTeaDistribution and deleteTeaDistribution, on the component release
because that is where the consumption schema puts release.distributions.

distributionId is absent from the request body by design: the server
derives it, and a publisher-supplied identifier would let one tenant
claim another's.

identifiers stays optional — Homebrew and Scoop have no accepted purl
type, and an identifier naming a coordinate that does not resolve is
worse than none, because a consumer will follow it.

Regenerated openapi.yaml. Redocly reports the same 24 errors as before,
all inherited from the upstream consumption specification.

Signed-off-by: Christopher Langton <chris@vulnetix.com>
Audited the overlay against a working TEA server and its console — Vulnetix
Resolve — rather than against itself. Five things the specification could not
express, each of which the implementation had to answer on its own.

A product release could not state its components. The consumption API lists
`components` among productRelease's required members, so every release a
conformant publisher could create had to be served with an empty list: a claim
about the product rather than a gap in the record.

An artifact could not say which distributions it describes. `distributionIds`
exists in the consumption artifact and was unreachable from the publication
side, which matters exactly where an SBOM is not one document.

There was no publisher read surface, and the consumption API cannot substitute
for one: it answers what a reader is entitled to see, so an object correctly
withheld and an object that was never created are indistinguishable from
outside. `GET /publications` and `/publications/{uuid}/releases` report what
was published alongside the policy in force for it.

`public` asked for "a separate confirmation" without naming one, so the
implementation invented `?confirm=public`. An unspecified requirement is one
every publisher satisfies differently, which is the failure this document
exists to prevent, so the parameter is now specified.

`publishTo` named targets nothing could configure and left failure semantics
undefined. Targets are now registered through `/publicationTargets`, mirroring
is asynchronous and never blocks the local write, per-target state is reported
on the access policy, and a server that does not mirror answers
`MIRRORING_UNSUPPORTED` instead of accepting the request and discarding it.

One change to the consumption specification: an empty alternative in the global
`security`. Without it `visibility: public` cannot mean what it says, because
discovery has no step at which a credential could be obtained and a server that
answers an anonymous request is non-conformant for doing so. It is a widening,
so nothing conformant breaks. build.mjs gives every overlay operation an
explicit credential requirement in return, so anonymous writes stay
non-conformant.

Redocly still reports the same 24 errors as the consumption specification
alone; the overlay adds none.

Signed-off-by: Christopher Langton <chris@vulnetix.com>
@0x73746F66 0x73746F66 changed the title updated publisher spec Proposed publisher API, generated from the consumption specification Aug 2, 2026
@0x73746F66
0x73746F66 marked this pull request as ready for review August 2, 2026 10:43
The step has failed on every run since it was added, and not for the reason
it looks like: `npx redocly lint` exits non-zero whenever it reports an
error, both documents report 24 by inheritance, and GitHub Actions runs the
step under `bash -e`. With `set -o pipefail` the pipeline inherits that
exit status, so errexit killed the script at the first assignment, before
either count was echoed or compared. The log showed an exit code and no
output, which is what made it read as a lint regression.

Discard redocly's exit status in the helper, where the count already
carries everything the status would have told us.

Two things worth keeping while in here. Empty output now fails loudly
rather than counting as zero errors, because a gate that passes when the
linter did not run is worse than no gate. And the diagnostic lint in the
failure branch gets `|| true`, so errexit cannot cut it short before the
explicit exit.

Verified against the step body extracted from the workflow itself, under
`bash -e`: equal counts exit 0; a linter producing no output exits 1 with a
message; a publisher document carrying one extra error exits 1 and reports
the delta.

Signed-off-by: Christopher Langton <chris@vulnetix.com>
The inheritance rule said a child may narrow what it inherits and must
not widen it, and described resolution as taking the nearest declared
policy. Those two statements do not combine into the guarantee the
document claims.

ACCESS_WIDENS_PARENT rejects a declaration that is too wide at the moment
it is written. It says nothing about a parent narrowed afterwards. So
under nearest-wins, an artifact that had declared public goes on being
public once its product is set to private, because the artifact's own
declaration is the nearest one and therefore becomes the answer. Marking
a product private is then a statement about a single row, which is the
opposite of what the section says it is for.

Resolution is now the narrowest declaration anywhere between the object
and the root. Where several levels declare shared, entitlements intersect
and the earliest expiry applies, since a grant on a child cannot create
an entitlement its parent withheld.

Descendants' declarations are explicitly not rewritten when an ancestor
narrows. Folding is reversible and rewriting is not: widening a product
back restores each descendant to its own declaration and no further,
which is what makes a temporary embargo something a publisher can undo.

Redocly still reports the same 24 errors as the consumption
specification alone.

Signed-off-by: Christopher Langton <chris@vulnetix.com>
Every em dash in the publisher overlay, its README, the generator and the
one comment added to the consumption specification is rewritten. Each was
replaced according to the grammar of its own sentence, using a colon, a
comma, a full stop or a parenthesis as the clause required, rather than by
substituting one character for another.

Wording only. `npm run check` reports the document still in sync, and
redocly reports the same 24 errors on both documents.

Signed-off-by: Christopher Langton <chris@vulnetix.com>
@0x73746F66

Copy link
Copy Markdown
Author

@oej i've added open questions in the PR description, but so far as having a consumer compliant publisher spec, generating and extending fromt he consumer is hard to argue agianst, if there is no objections we should merge this and break out the outstnadings into discrete work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants