@volter/twin-catalog 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ # Operate a catalog
2
+
3
+ Select the catalog and publisher repositories for the operation. Use the authorized production or test targets; these commands do not select public source scope or alter an existing World. The [process contract](process.md) owns admission authority; [contributor instructions](contributing.md) own independent publishing.
4
+
5
+ ## Responsibilities and access
6
+
7
+ `CODEOWNERS` and repository permissions name the moderators; current policy names authorized maintainers and direct trusted accounts. The operator records an incident owner and GitHub failure-notification recipients in the handoff. Repository ownership alone does not prove notifications are configured. An untrusted outside contributor and a distinct authorized human moderator are required to rehearse the outside review path. A trusted-account rehearsal does not require that review.
8
+
9
+ The catalog read App has administration, contents, checks and pull-request read access to the selected catalog. Its key is in `CATALOG_READ_APP_PRIVATE_KEY`; its client ID is a repository variable. The separate publisher App has contents and pull-request write access, without administration; its key is in `CATALOG_PR_APP_PRIVATE_KEY` in the trusted publisher. Both workflows mint target-scoped short-lived tokens and revoke them afterward. Contributors use their own GitHub and npm authority, never these keys. Registry publication uses trusted publishing or the configured `NPM_TOKEN`. Record registry package/workflow scope separately from GitHub App scope.
10
+
11
+ ## Find and retain a failure
12
+
13
+ Set the selected repository names, then list runs in the repository that owns the failed phase:
14
+
15
+ ```sh
16
+ catalog_repository="<catalog-owner/catalog-repository>"
17
+ publisher_repository="<publisher-owner/publisher-repository>"
18
+ gh run list --repo "$publisher_repository" --workflow release.yml
19
+ gh run list --repo "$catalog_repository" --workflow check.yml
20
+ gh run list --repo "$catalog_repository" --workflow publish.yml
21
+ ```
22
+
23
+ Inspect the chosen run and retain its artifacts in a new directory:
24
+
25
+ ```sh
26
+ gh run view <run-id> --repo <owner/repository> --log-failed
27
+ gh run download <run-id> --repo <owner/repository> --dir <new-evidence-directory>
28
+ ```
29
+
30
+ Record source SHA, attempt, submission identity and PR head/base alongside the diagnostics. Pack artifacts retain bytes, submission data and the installed catalog CLI lock. Assessment artifacts retain the input-bound report, dependency lock and preparation/cleanup logs. Index publication retains its receipt; durable evidence is also bundled in the index and linked from the evidence release. A failed or missing report is never reconstructed as a pass.
31
+
32
+ ## Recover pack publication
33
+
34
+ An accepted upload is immutable. If `upload-accepted.json` exists, confirmation is unresolved, or an upload result is uncertain, never upload that version again. Inspect exact registry identity against retained bytes. A rerun of the original workflow uses `GITHUB_RUN_ATTEMPT > 1` and cannot upload:
35
+
36
+ ```sh
37
+ gh run rerun <publisher-run-id> --repo "$publisher_repository" --failed
38
+ ```
39
+
40
+ Its source, lock and rebuilt archive must still match the registry integrity. A mismatch refuses and needs investigation, not a changed version attached to old evidence. For retained-byte confirmation in a publisher that implements the retained-byte recovery command, use its exact checkout and retained `release/` directory; point `CATALOG_CLI` at the exact installed released CLI and run `node scripts/publish.mjs --confirm-only`. This path performs reads and writes its receipt without upload or proposal credentials.
41
+
42
+ Independent publishers may use different release tooling; they must preserve the same immutable bytes and read-only confirmation rule. The commands above describe the shipped Volter publisher workflow.
43
+
44
+ A new first-attempt dispatch can upload an absent version. Use it only after evidence proves a prior failure happened before upload, or for a newly prepared immutable version. It is not the recovery command for an accepted or uncertain upload.
45
+
46
+ Proposal recovery uses canonical retained submission JSON. The publisher workflow's `propose --send` returns the existing PR even if it is closed; it does not reopen it, approve it or create a duplicate. A rejected release is not silently resubmitted. Artifact fixes require a new version. A proposal transport failure needs no package upload.
47
+
48
+ ## Recover assessment
49
+
50
+ Preparation confirms both the exact-version registry view and npm's installation view. Visibility can lag independently. Confirmation is bounded by the existing measured registry policy, performs reads only and checks exact integrity. Only 404 and an otherwise valid installation document missing that version are retried. Permission errors, malformed identities and conflicting bytes remain refusals.
51
+
52
+ For an infrastructure failure, explicitly reassess the current PR:
53
+
54
+ ```sh
55
+ gh workflow run check.yml --repo "$catalog_repository" --ref main -f pr=<pr-number>
56
+ ```
57
+
58
+ The job uses trusted current source and reads candidate data at the exact head. A changed head or base needs fresh evidence. Check the resulting report and current-head readiness; an older success or local report is insufficient. Candidate defects need a new immutable package version. The automation neither approves nor merges.
59
+
60
+ Untrusted-account submissions need genuine current-head non-author human moderator approval. Direct trusted-account contributions, including forks, and eligible internal changes may use the named maintainer exception; every path still requires current-head readiness. Verify the author against `policy.trustedAccounts` using GitHub ID, login and type. Never use administrator merge to bypass missing untrusted-account review or a failed check.
61
+
62
+ ## Recover index publication
63
+
64
+ Retry current protected main after resolving the recorded cause:
65
+
66
+ ```sh
67
+ gh workflow run publish.yml --repo "$catalog_repository" --ref main
68
+ ```
69
+
70
+ The workflow verifies admission and protection, then reuses a published version only when source and digest match. It cannot overwrite a conflicting identity. Retain the new receipt and compare registry integrity before calling the upload confirmed. No platform build, candidate test or hosted deployment is part of this job.
71
+
72
+ ## Recommend, reject or revoke
73
+
74
+ A moderator rejects a PR with a concrete reason. An unmerged readiness success adds no release to the index.
75
+
76
+ With multiple live implementations, change `recommendations.json` in a separate maintainer PR to select the package. Versions from different publishers never compete. Selection within that package offers its newest live stable approved release.
77
+
78
+ To withdraw an admitted release, append its exact package/version and a nonempty reason to `revocations.json` in a maintainer PR. Remove or replace a recommendation that would otherwise have no selectable release. After current-head readiness and moderator merge, index publication changes future defaults. It preserves immutable package bytes, prior evidence and existing World pins; it does not remotely uninstall a pack.
79
+
80
+ ## Pause distribution and replace credentials
81
+
82
+ Pause future work without deleting artifacts or rewriting history:
83
+
84
+ ```sh
85
+ gh variable set PACK_PUBLISH_ENABLED --repo "$publisher_repository" --body false
86
+ gh variable set CATALOG_PUBLISH_ENABLED --repo "$catalog_repository" --body false
87
+ ```
88
+
89
+ These flags govern future jobs; they do not recall published packages or settle an in-flight upload. Preserve its receipts and inspect the outcome separately. Re-enable the intended flag only after the incident is resolved.
90
+
91
+ For an App key replacement, inventory selected repositories and consumers, generate an additional key, store it directly in the correct encrypted repository secret, and confirm the intended workflow's access before revoking the old key. Never print or commit a key, broaden an installation to every repository, or remove another consumer's key. Keep read and proposal roles separate. For npm replacement, use approved custody or trusted publishing and restrict package/workflow scope appropriately. Document identity, target and replacement outcome without secret values.
@@ -0,0 +1,308 @@
1
+ # Catalog process
2
+
3
+ This repository operates admission and distribution of twin packages from independent publishers. The platform owns
4
+ the runtime, Protocol 3 and the versioned standard. Publishers own their source and npm releases. This repository
5
+ owns source registration, submissions, evaluation policy, moderator decisions and the approved index. It requires
6
+ released tools, not a platform checkout. Its executable contract is the modules in `lib/` and the workflows in
7
+ `.github/workflows/`; `bin/twin-catalog.mjs` exposes the same operations to contributors and automation.
8
+
9
+ ## Identity and authority
10
+
11
+ A vendor is a service being simulated. An implementation is an npm package. A release is an exact package version
12
+ with a SHA-512 integrity value. A source is a registered GitHub repository, npm scope and release-workflow identity.
13
+ One repository may publish several packages. Several repositories may implement the same vendor. Package names are
14
+ not vendor identities. Version ordering applies only within one package, never between competing implementations.
15
+
16
+ `sources.json` registers sources. Registration is its own separately authorized PR; a submission cannot grant its source
17
+ trust, change evaluation tools, or change catalog policy. `official` identifies Volter maintenance, not a verification
18
+ exemption or vendor endorsement. Every new release follows the same readiness requirements. Contributions from untrusted accounts
19
+ require current-head human moderator approval. Direct submissions from an account explicitly listed in
20
+ `policy.trustedAccounts` need no separate moderator review, including fork PRs. Each entry binds the numeric
21
+ GitHub account ID, exact login and account type (`User` or `Bot`). Trust is checked against the PR author returned
22
+ by GitHub, never a commit author, fork owner, organization membership or submission field. A maintainer named in
23
+ `policy.reviewBypassUsers`, with current `admin` or `maintain` permission, merges after current-head readiness.
24
+ Publication records this path as `trusted-account-merge` with the submitting account identity; it does not claim
25
+ human review. Trust does not exempt source registration, artifact integrity, provenance or assessment.
26
+ Maintainers may merge their own changes without a separate review.
27
+ Internal admissions require the registered repository to be explicitly listed in `policy.internalRepositories`,
28
+ a PR branch in this catalog repository, and a merge by a maintainer named in `policy.reviewBypassUsers` whose current
29
+ GitHub permission is `admin` or `maintain`. The author must be a repository member/collaborator or its GitHub Actions
30
+ bot, or a publisher bot explicitly named in `policy.internalBotAuthors`. Each configured publisher bot binds both
31
+ its GitHub user ID and exact login; its App registration ID is a different identity. An unconfigured or mismatched
32
+ bot remains outside the internal path. A bot author or `official` badge alone does not make an outside publisher internal.
33
+ Public-source npm provenance is required for new admissions. Existing index records without an assessment remain
34
+ historical records, explicitly unassessed; migration does not manufacture evidence or reapprove them. A separate
35
+ `reassessments/<id>.json` PR uses the same immutable submission schema, checks and moderator review to assess an
36
+ unassessed historical release without rewriting its admission commit. Missing original provenance still fails;
37
+ publishing and admitting a new version is the repair.
38
+
39
+ `recommendations.json` explicitly selects the default package when a vendor has multiple live implementations.
40
+ One live implementation is unambiguous. With several and no recommendation, resolution refuses to guess. A
41
+ recommendation is moderated separately from a submission and selects a package, whose newest stable approved
42
+ version is offered. Prereleases require an explicit version. A World pins package and version; a catalog update
43
+ does not change that pin. Removing a recommendation is not uninstalling a running World.
44
+
45
+ ## Submission
46
+
47
+ A release PR adds exactly one `submissions/<id>.json`, with no executable files or index edits:
48
+
49
+ ```json
50
+ {
51
+ "schemaVersion": 1,
52
+ "source": "example-team",
53
+ "vendor": "stripe",
54
+ "package": "@example/stripe-simulator",
55
+ "version": "1.2.3",
56
+ "integrity": "sha512-BASE64_OF_THE_ARTIFACT_DIGEST"
57
+ }
58
+ ```
59
+
60
+ The id is the SHA-256 of the canonical submission fields. The CLI reads registry metadata and creates the file;
61
+ it does not run package code, publish a package, open a PR, or send a message. The author opens the PR normally.
62
+ The registry is catalog policy, not contributor input. Tags, ranges, URLs, git dependencies and mutable versions
63
+ are not release identities. Artifact package.json must match the submitted package and version, declare a license,
64
+ and name the registered repository. Generated pack facts must identify exactly the submitted vendor and Protocol 3.
65
+ An already admitted package/version cannot acquire different bytes or a different vendor/source.
66
+
67
+ The catalog evaluates only the tarball fetched at the declared integrity. Package source in the contributor's
68
+ repository and evidence supplied in the PR are context, never substitutes for running that artifact. A submission
69
+ cannot select its own evaluator, thresholds, registry, commands or privileges.
70
+
71
+ ## State and invalidation
72
+
73
+ | State | Evidence | Next action |
74
+ |---|---|---|
75
+ | Submitted | A release PR with valid data | Automation resolves the artifact |
76
+ | Checking | Current-head readiness check in progress | Automation evaluates |
77
+ | Changes needed | Validation, evaluation or replay failed | Contributor updates and resubmits |
78
+ | Ready | Required checks passed for this head and policy | Untrusted authors obtain moderator approval; trusted accounts proceed to authorized maintainer merge |
79
+ | Approved | Required checks remain current; untrusted authors have current-head review, or an explicit trusted-account/internal exception applies | Authorized maintainer merges |
80
+ | Admitted | Authorized merge on protected main through human review, trusted-account admission or the internal maintainer path | Publication builds the index |
81
+ | Rejected | Moderator closes the PR with a reason | New submission if corrected |
82
+
83
+ Readiness is not approval. Catalog assessment never approves or merges. Review dismissal on new commits, required current-head checks,
84
+ CODEOWNERS review and a branch rule requiring the branch to be current enforce the decision. A moderator's identity
85
+ comes from GitHub review and merge events, never a contributor's `by` string. Self-approval is not accepted as review.
86
+ Named maintainers have a review bypass for internal changes and direct trusted-account contributions; untrusted
87
+ admissions still need human review. Every path needs current-head readiness. Publication records which admission path and merger established authority.
88
+ The repository's installation command checks/configures these rules explicitly; merely committing a workflow is not
89
+ evidence that they are enabled. No credentials or repository settings are changed by an ordinary build.
90
+
91
+ An assessment binds the PR number and head SHA, base SHA, source registration, submission, policy, evaluator package
92
+ versions, container image identity, artifact integrity and resolved dependency lock. Evidence from an earlier head
93
+ does not satisfy a new one. A base update requires updating the branch and a new readiness run. Jobs cancel superseded
94
+ runs; their historical reports remain evidence only. A merged policy change affects subsequent admission runs; it
95
+ does not retroactively certify old releases under the new policy. Publication checks the admitted submission identity. Identical reruns retain their inputs and produce comparable
96
+ results. A failed or interrupted run has no successful readiness receipt.
97
+
98
+ ## Execution boundary
99
+
100
+ PR data is read using the GitHub API from the exact head. Trusted automation is checked out from the base commit,
101
+ never the contributor's head. Source-registration and maintenance PRs do not execute candidate scripts. All package
102
+ extraction, dependency installation and evaluation occur in disposable containers with no repository, npm, cloud,
103
+ moderation or publication credentials, no Docker socket and no host project mounts. Install scripts are disabled.
104
+ Dependencies are resolved once into a recorded lock; evaluation reuses that installation with network disabled.
105
+ Before dependency installation, preparation confirms the candidate's exact version and integrity in npm's
106
+ installation metadata as well as its exact-version metadata. These registry views may propagate separately.
107
+ Only HTTP 404 or an otherwise valid package document without that version is retried, using the existing measured
108
+ registry confirmation window. HTTP errors, malformed identities and conflicting integrity fail immediately.
109
+ Confirmation performs reads only; it never uploads, grants readiness or retries candidate installation scripts.
110
+ Input data is readable by the container's unprivileged user. Setup and preparation failures produce a failed JSON
111
+ envelope as well as diagnostics; neither can establish readiness. Owner-dispatched verification walks a synthetic
112
+ browser transport fixture through the same offline boundary before assessing an actual artifact. The fixture checks
113
+ fresh cookie jars, complete request headers, binary bodies, empty responses, CORS and replay equality; it grants no admission.
114
+ The versioned standard owns this fixture. Missing CORS permission remains a denial even when Playwright would add
115
+ mock-response permission. Context teardown is awaited before the next replay.
116
+ A separate public-tooling fixture verifies a real npm SLSA bundle and rejects substituted bytes and repositories.
117
+ Process verification also builds and installs the index tarball and runs its consumer CLI in a directory without
118
+ platform packages or pack checkouts. This checks distribution packaging without claiming a registry publication.
119
+ Registry publish receipts are distinct from repository provenance; SHA-512 subjects emitted by npm and SHA-256
120
+ subjects are checked against the downloaded artifact, with conflicting declared digests refused.
121
+ The released standard declares its separate SDK fixture directories. Preparation installs those trusted, frozen
122
+ locks with scripts disabled and retains them in the receipt, so their client cases need no installation offline.
123
+ Owner verification may expect the specific missing-provenance refusal for a historical fixture. That verifies the
124
+ negative path while retaining its changes-needed envelope; it cannot create the admission workflow's readiness check.
125
+ Preparation's registry reads and evaluation's offline phase are reported separately. A World alone is cooperative
126
+ routing, not the isolation boundary: the container enforces the evaluation network boundary.
127
+
128
+ The trusted controller owns success/failure, hashes and run identity. Candidate output is untrusted data; bounded
129
+ report parsing never executes it or interpolates it into a shell. It can explain results but cannot confer merge or
130
+ publication authority. The trusted summary renderer escapes contributor strings and neutralizes mentions. Raw logs
131
+ and JSON evidence are downloadable artifacts. There is no execution of arbitrary contributor-supplied test commands.
132
+ Conformance and journey checks are the versioned standard's own entrypoints. Like any in-process plugin check, they
133
+ measure cooperative pack behavior, not a proof that malicious executable code cannot deceive a test; moderator
134
+ source review remains part of admission.
135
+
136
+ Provenance verification checks the registry signature/attestation using npm's verifier, cryptographically verifies the exact downloaded Sigstore bundle, and checks the attested
137
+ repository/workflow against the registered source. A certificate-shaped string alone is not verification. The
138
+ artifact digest is independently recomputed. Private-source publication without provenance cannot claim readiness.
139
+
140
+ ## Evaluation and reports
141
+
142
+ The versioned `@volter/twin-standard` owns the checks and machine-readable assessment API. The catalog invokes it;
143
+ it does not implement another Protocol 3 grader or journey engine. Evaluation occurs inside a task-owned World.
144
+
145
+ Quick assessment walks the packaged customer journey twice from fresh state with the same clock and World draws.
146
+ It reports failures, answered steps, response replay equality, and served/gap counts over the entire declared surface. Actual operation coverage comes from kernel dispatch, with
147
+ every declared gap retained in the denominator; declared support and exercised coverage are separate fields.
148
+ An empty journey or missing response trace cannot establish replay. Unsupported instrumentation is `null`/not measured,
149
+ never zero or 100%. Quick assessment does not claim browser DOM coverage, real-vendor parity, line coverage, or complete
150
+ state-transition coverage. Those require their own instruments. HTTP-only journeys also replay twice through Chromium's fetch, cookie jar and HTTP response behavior, using the same
151
+ walker and operation observer. Mixed or non-HTTP wires report browser measurement as unavailable. This target measures
152
+ no DOM interactions or real-vendor parity. World and JavaScript clocks are frozen; Chromium's process wall time follows
153
+ World time through libfaketime, at one-second native precision. Monotonic timers retain elapsed machine time. Verification
154
+ checks both native `Expires` and `Max-Age` across a World clock advance; no expiry headers are rewritten. The exact
155
+ Chromium version, clock precision and browser replay results are reported.
156
+
157
+ Admission adds form checks for every unit and full deterministic conformance. A failure in any required check blocks
158
+ readiness; a grade percentage is informational, not a threshold. Missing optional coverage is shown explicitly.
159
+ Comparison to the previous approved version names added/removed served operations, failure counts and changed denominators;
160
+ it never compares two different publishers' percentages as though they share a surface. A missing baseline is
161
+ reported, not treated as no regression. Evaluation reports and catalog output are JSON for later page integration.
162
+
163
+ Reports identify measurement scope, inputs, tool versions and execution target. Publisher evidence and independent
164
+ catalog measurements remain distinguishable. A moderator may reject an otherwise passing submission for implausible
165
+ journeys, misleading scope, provenance problems, maintenance concerns or source defects, with a concrete reason.
166
+
167
+ ## Discovery and consumer integration
168
+
169
+ The npm artifact is the distribution interface. `catalog.json` schema 1 lists every recorded vendor/package/version,
170
+ its source, status, integrity when known, and evidence reference. `assessed: false` and `evidence: null` explicitly mark
171
+ historical records. Consumers read `recommendations.json` and `revocations.json` beside it. Displaying a recorded
172
+ release is different from offering it as an install default: pending, rejected, revoked and prerelease versions are
173
+ never automatic selections. A missing or failing assessment is never inferred from a publisher badge.
174
+
175
+ A future catalog page groups implementations under a vendor, attributes the repository and publisher, and links to
176
+ the immutable version and admission PR/evidence. It can display readiness, support counts, journey failures and replay
177
+ results separately. A contributor sees the PR check, one updated feedback comment, and downloadable workflow artifacts.
178
+ Readiness checks bind the report digest, workflow run ID and attempt in their external receipt. GitHub may replace
179
+ the display URL and attach a manually dispatched check to an older PR check suite; that display association is not
180
+ the report's source. After authorized merge, publication fetches the exact recorded workflow attempt and requires
181
+ success, the trusted readiness workflow, and the report's bound input identity. A manual dispatch runs at the bound
182
+ trusted base; a pull-request-target run names the submitted head. Older receipts retain their validated run URL or
183
+ unique check-suite/head association. An unrelated workflow, source commit, attempt or substituted artifact cannot
184
+ supply evidence. The publisher then validates
185
+ the report against that receipt and stores it in a GitHub evidence release and in the index package's `evidence/`.
186
+ Reports include the resolved dependency lock. Workflow logs remain supplementary artifacts with configured retention.
187
+ If evidence expires before its first durable copy, publication refuses; it never reconstructs a passing report.
188
+ This process supplies data for a browse page and does not deploy one.
189
+
190
+ `volter world init` uses the installed catalog's selected exact package version when installing a missing vendor.
191
+ Already installed pack identities come from their generated facts. Multiple installed implementations require a
192
+ recommendation or an explicit World package choice; unrelated version numbers cannot resolve that choice. Existing
193
+ World pins remain subject to the runtime's normal version checks. Revocation changes future catalog selection, not
194
+ an existing World's declared dependency; the catalog cannot remotely uninstall code from an application.
195
+
196
+ ## Merge and publication
197
+
198
+ The publication workflow runs only when the repository variable `CATALOG_PUBLISH_ENABLED` is `true`; activation
199
+ follows successful Actions verification and moderator configuration. Protected main is the admission ledger. Submission files are immutable after admission. The publisher rebuilds from
200
+ the existing index and merged submissions, adding versions without altering historical identity. It writes
201
+ `sources.json`, `vendors/`, `recommendations.json` and machine-readable catalog metadata into the package artifact.
202
+ Each admitted version links to its submission and merge commit; the GitHub PR retains reviews and readiness artifacts.
203
+ Index generation does not import a pack, execute tests or rebuild the platform. The website and hosted runtime are
204
+ independent consumers of the published index; their absence or build failures do not block index publication.
205
+
206
+ The publisher verifies branch protection and merger permissions with read-only GitHub App access, separate from
207
+ its index/evidence write token. Install the App only on the catalog repositories with administration, contents,
208
+ checks and pull requests set to read. Store its client ID in `CATALOG_READ_APP_CLIENT_ID` and private key in
209
+ `CATALOG_READ_APP_PRIVATE_KEY`. The pinned token action requests those permissions only for the current catalog
210
+ repository and revokes its installation token after the job. The key is available only to the trusted publication
211
+ job; candidate preparation and evaluation remain credential-free. A preconfigured equivalent `CATALOG_READ_TOKEN`
212
+ is supported for existing operators. Missing read access fails admission verification and cannot bypass it.
213
+
214
+ Trusted pack repositories use a separate publisher App to propose immutable data PRs. Install that App only on the
215
+ target catalogs with contents and pull requests set to write, without administration. Store its client ID as
216
+ `CATALOG_PR_APP_CLIENT_ID` and key as `CATALOG_PR_APP_PRIVATE_KEY` in the trusted publisher repository, never in
217
+ candidate evaluation. Its workflow mints a short-lived token restricted to the target catalog and revokes it after
218
+ proposal. App-authored PR events start catalog readiness through GitHub's normal event flow; the publisher does not
219
+ dispatch or choose an evaluator. Existing scoped `CATALOG_PR_TOKEN` deployments remain supported. Outside publishers
220
+ do not receive Volter's App key: they register their own source and submit through their own GitHub account or fork.
221
+
222
+ Publication is serialized and starts from a clean checkout of current protected main. An obsolete queued job refuses
223
+ to publish, so it cannot move the registry default backwards; retry on current main includes its admitted releases.
224
+ A release identifies its source commit and npm artifact; retrying the same commit reuses
225
+ the same version, verifies identity if it already exists, and does not publish a duplicate. A registry conflict with
226
+ different identity fails. A publish failure leaves the admitted ledger intact and is retryable; it does not mark
227
+ anything deployed. Credentialed publication never evaluates candidate code. Rejection records remain in closed PRs;
228
+ they are not added to the resolvable index. Revocation of an admitted version is an explicit moderator-maintained
229
+ record with a reason; it removes the version from future selection without rewriting its artifact or existing pins.
230
+
231
+ After npm accepts an upload, publication confirms the exact version with read-only registry requests. A temporary
232
+ 404 is pending propagation, not a second upload: confirmation retries every 15 seconds for up to ten minutes.
233
+ Matching source identity and integrity are still required; conflicting identity and other HTTP refusals fail
234
+ immediately. Exhausted confirmation reports an unverified upload and leaves the admitted ledger intact.
235
+ The window rounds up three times the observed confirmation delay; it is not a registry availability guarantee. Its
236
+ [captured measurement](measurements/registry-confirmation.json) records the source commit, real Actions mode,
237
+ concurrent work, commands and UTC observations; repeat it on a fresh sandbox version when registry behavior changes.
238
+
239
+ ## Contribution and operation
240
+
241
+ ### Read the published catalog
242
+
243
+ Catalog discovery reads an installed index package through `browse` or the package's `./browse` export. It reads
244
+ JSON only, without Git, network access, platform login or candidate imports. The adapter checks the package/catalog
245
+ source and digest against the index records and evidence references, then checks each bundled report against its
246
+ recorded digest and release identity. Package tarball integrity remains the installer's responsibility; the caller
247
+ may retain its verified lockfile integrity with the snapshot, but the adapter does not manufacture it.
248
+
249
+ The projection preserves every implementation and version, including historical, pending, rejected and revoked
250
+ records. Selectability, recorded status, assessment availability and default choice are separate fields. Selection
251
+ uses the existing catalog default rule for each vendor. A vendor with competing packages remains browsable with
252
+ `choice-required`; ambiguity does not hide the other vendors or compare versions across publishers.
253
+
254
+ Measurements come only from checksum-bound catalog reports. Declared surface, dispatched operation coverage,
255
+ journey failures, replay and browser execution retain their scopes and denominators. Missing measurements are
256
+ `null`; coverage is never an admission score. Contribution links point to this catalog's repository and published
257
+ instructions. This read projection grants no admission authority and does not replace the canonical schema-1 data.
258
+
259
+ ### Prepare a source registration
260
+
261
+ `register` prepares the one-source addition to a selected catalog snapshot's `sources.json`. It validates the same
262
+ source fields as assessment, refuses an existing conflicting source or a different output file, and writes data
263
+ only. Run it in a fork or pass a new output path when using an installed package. Source registration remains a
264
+ separate PR; the command neither publishes a package nor opens, approves or merges that PR.
265
+
266
+ Contributors can create and index fixed pack files, derive their vendored spec, compile package facts and assess
267
+ with released `twin-standard` commands. Contributors need a public source repository, their own scoped npm package and the released SDK/standard. No Volter
268
+ checkout, private company record or particular pack-repository layout is required by the catalog. Source registration,
269
+ submission, checking, generation and publishing commands are documented in README. Published CLI tooling includes
270
+ the schema/validation code, so contributors see the same refusals before opening a PR.
271
+
272
+ Automation maintains one PR feedback comment, includes the report in the check summary, and updates it on reruns.
273
+ GitHub reads retry one transport failure; HTTP refusals and writes are not retried. A transport failure identifies
274
+ the method, repository path and underlying error code. A passing evaluation remains retained evidence until GitHub
275
+ successfully records its current-head readiness check; it does not grant admission on its own.
276
+ Manual workflow dispatch can reassess a PR after an external failure. Moderators configure membership through the
277
+ repository's CODEOWNERS users or teams; platform write access is not required. Repository setup and npm publication are explicit
278
+ operator actions. Deploy tokens, website changes, making repositories public, and operating GitHub settings are not
279
+ performed as part of implementing this process.
280
+
281
+ ## Acceptance cases
282
+
283
+ The acceptance contract below spans process fixtures and the isolated evaluator. Local fixture results cover only
284
+ the cases they exercise; container/provenance execution and browser execution must be reported separately. Verification
285
+ uses synthetic inputs where possible and never real publication:
286
+
287
+ 1. An independently scoped, arbitrarily named package maps to its declared vendor and remains pinned when used.
288
+ 2. Two packages for one vendor are retained; no cross-package semver comparison chooses the winner. Recommendation
289
+ selects the default; absent or revoked choices fail visibly.
290
+ 3. A submission cannot alter source trust, policy, workflows, commands, index records or an admitted submission.
291
+ 4. Changed bytes, package identity, vendor facts, provenance, head, base or evaluator invalidate the corresponding
292
+ assessment. Failed, cancelled or missing checks never become ready or admitted.
293
+ 5. Empty/mismatching replay fails; passing checks and partial surface remain separate measurements.
294
+ 6. Untrusted-account admissions require current-head human moderator review after readiness; approval of an old head
295
+ and contributor-supplied reviewer names cannot admit a release. A directly trusted account may omit a separate
296
+ review, including from a fork; account ID, exact login and type must match policy. The separate internal publisher
297
+ path needs an explicitly trusted source and a same-repository PR. Both exceptions require a named authorized
298
+ maintainer merge with verified current permission. Every path requires current-head readiness; fork ownership,
299
+ a bot author or an official badge alone grants no exemption. Catalog workflows have no merge path.
300
+ 7. Merging adds one immutable release, preserves other publishers and pins, and produces an index without a platform
301
+ checkout, hosted build or website.
302
+ 8. Publication retry is idempotent; a conflicting published identity fails, and a revoked release is not recommended.
303
+
304
+ Results state exactly what ran. Static workflow inspection and local fixture checks are not a real GitHub/npm
305
+ activation, and a local quick assessment is not a browser fidelity result.
306
+
307
+ After npm accepts an upload, publisher submission preparation may use `submit --confirm-published`. It waits only
308
+ for exact-version HTTP 404 propagation, with the same measured bound as index confirmation, and performs no upload.
@@ -0,0 +1,75 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { classify, loadIndex, read, write, digest, requireThat, validateIndex, compareReports, compareVersions } from './model.mjs';
5
+ import { builtIndex } from './publication.mjs';
6
+ import { evaluate } from './runner.mjs';
7
+ import { summary, feedback } from './github.mjs';
8
+
9
+ export async function assessPullRequest(root, github, number, output, evaluator = evaluate) {
10
+ requireThat(Number.isSafeInteger(number) && number > 0, 'PR number must be a positive integer');
11
+ const pr = await github.call(`pulls/${number}`);
12
+ requireThat(pr.state === 'open', 'PR is not open');
13
+ const local = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' });
14
+ requireThat(local.status === 0 && local.stdout.trim() === pr.base.sha, 'trusted checkout must match the current PR base; update/retry the workflow');
15
+ const head = pr.head.sha;
16
+ const check = await github.call('check-runs', 'POST', { name: 'catalog/readiness', head_sha: head, status: 'in_progress', external_id: `${number}:${head}:${pr.base.sha}` });
17
+ const input = { schemaVersion: 1, repository: github.repository, pullRequest: number, head, base: pr.base.sha, policy: read(join(root, 'policy.json')) };
18
+ let envelope;
19
+ try {
20
+ const files = await github.pages(`pulls/${number}/files`);
21
+ requireThat(files.length === pr.changed_files, 'GitHub did not return the complete change; refuse truncated PR data');
22
+ const contents = {};
23
+ for (const file of files) if (/^(submissions|reassessments)\/|^(sources|recommendations|revocations)\.json$/.test(file.filename) && file.status !== 'removed') contents[file.filename] = await github.content(file.filename, head);
24
+ const index = builtIndex(root);
25
+ const kind = classify(files, index, contents);
26
+ input.kind = kind.kind;
27
+ if (['submission', 'reassessment'].includes(kind.kind)) {
28
+ input.submission = kind.submission; input.source = kind.source;
29
+ envelope = await evaluator(root, input, output);
30
+ const previous = index.vendors.find((v) => v.vendor === kind.submission.vendor)?.packages.find((p) => p.name === kind.submission.package)?.versions.filter((v) => v.status === 'live' && v.integrity && !v.version.includes('-')).sort((a, b) => compareVersions(b.version, a.version))[0];
31
+ if (previous && envelope.report) {
32
+ try {
33
+ const baselineInput = { ...input, submission: { ...kind.submission, version: previous.version, integrity: previous.integrity }, purpose: 'baseline' };
34
+ const baseline = await evaluator(root, baselineInput, `${output}.baseline.json`);
35
+ envelope.report.comparison = compareReports(envelope.report, baseline.report);
36
+ } catch (error) { envelope.report.comparison = { baseline: previous.version, measured: false, note: `Baseline could not be measured: ${error.message}` }; }
37
+ }
38
+ } else {
39
+ // Maintainers decide maintenance; never execute the PR's candidate workflows.
40
+ envelope = { schemaVersion: 1, input, inputSha256: digest(input), status: 'ready', report: null, note: `${kind.kind}: data validation only; no package assessment` };
41
+ }
42
+ } catch (error) {
43
+ envelope = { schemaVersion: 1, input, inputSha256: digest(input), status: 'changes-needed', error: String(error.message ?? error) };
44
+ }
45
+ const current = await github.call(`pulls/${number}`);
46
+ if (current.head.sha !== head || current.base.sha !== input.base || current.state !== 'open') { envelope.status = 'superseded'; envelope.error = 'PR head, base or state changed during assessment; current input needs a new run.'; }
47
+ envelope.workflow = process.env.GITHUB_RUN_ID ? { repository: github.repository, run: Number(process.env.GITHUB_RUN_ID), attempt: Number(process.env.GITHUB_RUN_ATTEMPT ?? 1), file: 'check.yml' } : null;
48
+ write(output, envelope);
49
+ const body = summary(envelope);
50
+ const workflow = envelope.workflow;
51
+ requireThat(!workflow || (Number.isSafeInteger(workflow.run) && workflow.run > 0 && Number.isSafeInteger(workflow.attempt) && workflow.attempt > 0), 'workflow receipt needs a valid run and attempt');
52
+ const receipt = [number, head, input.base, digest(envelope), ...(workflow ? [workflow.run, workflow.attempt] : [])].join(':');
53
+ await github.call(`check-runs/${check.id}`, 'PATCH', { external_id: receipt, ...(envelope.workflow ? { details_url: `https://github.com/${github.repository}/actions/runs/${envelope.workflow.run}` } : {}), status: 'completed', conclusion: envelope.status === 'ready' ? 'success' : envelope.status === 'superseded' ? 'cancelled' : 'failure', output: { title: `Catalog ${envelope.status}`, summary: body } });
54
+ if (envelope.status !== 'superseded') await feedback(github, number, body);
55
+ return envelope;
56
+ }
57
+
58
+ /** Explicit operator command; never called by an assessment or publication job. */
59
+ export async function configure(github, apply = false, reviewBypassUsers = []) {
60
+ requireThat(Array.isArray(reviewBypassUsers) && reviewBypassUsers.every((u) => typeof u === 'string' && /^[A-Za-z0-9-]+$/.test(u)), 'invalid internal maintainer usernames');
61
+ const desired = {
62
+ required_status_checks: { strict: true, contexts: ['catalog/readiness'] },
63
+ enforce_admins: true,
64
+ required_pull_request_reviews: { dismiss_stale_reviews: true, require_code_owner_reviews: true, required_approving_review_count: 1, require_last_push_approval: true, bypass_pull_request_allowances: { users: reviewBypassUsers, teams: [], apps: [] } },
65
+ restrictions: null, required_conversation_resolution: true,
66
+ allow_force_pushes: false, allow_deletions: false,
67
+ };
68
+ if (apply) {
69
+ const owners = await github.call('codeowners/errors?ref=main');
70
+ requireThat(Array.isArray(owners.errors) && owners.errors.length === 0, 'CODEOWNERS is invalid; configure real moderators with repository write access before protecting admission');
71
+ await github.call('branches/main/protection', 'PUT', desired);
72
+ await github.call('actions/permissions', 'PUT', { enabled: true, allowed_actions: 'all' });
73
+ }
74
+ return { applied: apply, branch: 'main', protection: desired, actionsEnabled: apply };
75
+ }
@@ -0,0 +1,80 @@
1
+ export type Coverage = {
2
+ total: number;
3
+ exercised: number;
4
+ percent: number;
5
+ operations: string[];
6
+ missing: string[];
7
+ };
8
+ export type Replay = { equal: boolean; runs: number; firstSha256: string; secondSha256: string };
9
+ export type Measurements = {
10
+ scope: string | null;
11
+ surface: { total: number; served: number; gap: number } | null;
12
+ operationCoverage: Coverage | null;
13
+ servedOperations: string[] | null;
14
+ steps: number | null;
15
+ answered: number | null;
16
+ failures: unknown[] | null;
17
+ replay: Replay | null;
18
+ conformance: Array<{ id: string; asks: string; failures: unknown[]; note?: string }> | null;
19
+ browser: {
20
+ measured: true;
21
+ target: string;
22
+ version: string;
23
+ answered?: number;
24
+ failures?: unknown[];
25
+ operationCoverage?: Coverage;
26
+ replay?: Replay;
27
+ domCoverage?: unknown | null;
28
+ clock?: { native: string; nativePrecisionMs: number; javascriptPrecisionMs: number; monotonic: string };
29
+ } | null;
30
+ codeCoverage: unknown | null;
31
+ stateTransitionCoverage: unknown | null;
32
+ domCoverage: unknown | null;
33
+ comparison: Record<string, unknown> | null;
34
+ };
35
+ export type Release = {
36
+ version: string;
37
+ status: 'live' | 'pending' | 'rejected';
38
+ integrity?: string;
39
+ commit?: string;
40
+ at?: string;
41
+ by?: string;
42
+ reason?: string;
43
+ submission?: string;
44
+ assessmentCommit?: string;
45
+ assessedAt?: string;
46
+ };
47
+ export type CatalogRelease = Release & {
48
+ revoked: { reason: string } | null;
49
+ selectable: boolean;
50
+ default: boolean;
51
+ assessment: {
52
+ recorded: boolean;
53
+ available: boolean;
54
+ evidence: null | { submission: string; admissionCommit?: string; reportPath?: string; reportSha256?: string;
55
+ url?: string; pullRequest?: number; moderation?: Record<string, unknown> };
56
+ input: null | { repository: string; pullRequest: number; head: string; base: string; image?: string;
57
+ workflow?: { repository: string; run: number; attempt: number; file: string };
58
+ tools: Record<string, string> | null };
59
+ };
60
+ measurements: Measurements | null;
61
+ };
62
+ export type CatalogView = {
63
+ schemaVersion: 1;
64
+ snapshot: { package: string; version: string; integrity: string | null; sourceCommit: string; digest: string };
65
+ contribution: { repository: string; registrationUrl: string; pullRequestsUrl: string; guideUrl: string };
66
+ vendors: Array<{
67
+ vendor: string;
68
+ recommendation: string | null;
69
+ selection: { state: 'selected' | 'unavailable-recommendation' | 'choice-required' | 'unavailable';
70
+ default: { package: string; version: string; source: string } | null };
71
+ implementations: Array<{
72
+ package: string;
73
+ source: { name: string; repository: string; scope: string; official: boolean; protocol: '3';
74
+ workflow?: string; url: string; workflowUrl: string };
75
+ versions: CatalogRelease[];
76
+ }>;
77
+ }>;
78
+ };
79
+ /** JSON-only installed-index reader. Does not fetch, import packs, assess, install or verify tarball integrity. */
80
+ export function browse(root: string, options?: { vendor?: string; package?: string; integrity?: string | null }): CatalogView;