Skip to content

Plugin registry labelling service#1909

Draft
ascorbic wants to merge 38 commits into
mainfrom
feat/plugin-registry-labelling-service
Draft

Plugin registry labelling service#1909
ascorbic wants to merge 38 commits into
mainfrom
feat/plugin-registry-labelling-service

Conversation

@ascorbic

@ascorbic ascorbic commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Establishes feat/plugin-registry-labelling-service as the integration branch for the plugin registry labelling service described in RFC #694.

This PR tracks the complete implementation without sub-issues. Implementation PRs should target feat/plugin-registry-labelling-service; this umbrella PR remains draft until the production launch gates in the implementation plan are complete.

The tracked planning documents are:

  • .opencode/plans/plugin-registry-labelling-service/spec.md
  • .opencode/plans/plugin-registry-labelling-service/implementation-plan.md

Related to #694.

Implementation tracker

Design and feasibility

Shared protocol and policy

Service foundation and signed distribution

Aggregator and client enforcement

Automated assessment

Operator and publisher surfaces

  • Implement Access JWT verification, role mapping, CSRF, and idempotent mutations
  • Build Kumo/Lingui operator dashboard, assessment, subject, audit, and system views
  • Implement reviewer release/package actions and reruns
  • Implement atomic false-positive overrides
  • Implement admin-only emergency takedown, publisher-compromise, pause, and DLQ actions
  • Publish machine-readable policy and public current/historical assessment APIs
  • Implement public/private serializers and contact resolution
  • Implement notification outbox, email delivery, and manual reconsideration workflow
  • Complete browser, accessibility, localization, and Arabic RTL coverage

Production readiness

  • Harden staging/production bindings, CI, deploy validation, and secrets handling
  • Add component metrics, dashboards, logs, and alerts
  • Complete key-management, incident, outage, replay, and recovery runbooks
  • Implement backup, restore, retention, and contact-data minimization
  • Document self-hosting and third-party labeller consumption
  • Complete protocol, registry, adversarial, query-count, performance, and browser conformance suites
  • Complete calibration review and external security review
  • Pass key compromise, D1 restore, cursor loss, full replay, queue, AI, mirror, and notification drills
  • Assess every first-party release and enable positive-assessment enforcement through the staged rollout

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs; a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

RFC #694 is the approved design source. Individual implementation PRs carry their applicable changesets and verification evidence. No admin translation catalogs are included.

AI-generated code disclosure

  • This PR includes AI-generated code; model/tool: OpenCode with GPT-5.6-sol

Screenshots / test output

Integrated gate on 2d6ad448 after #1952 merged:

  • pnpm build passes
  • pnpm typecheck and aggregator typecheck pass
  • Type-aware lint reports 0 warnings and 0 errors
  • Aggregator: 216 tests pass
  • Registry moderation: 66 tests pass
  • Labeler workerd integration: 29 tests pass
  • feat(registry): add label verification primitives #1952 completed two independent review rounds with no remaining high or medium findings

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks feat/plugin-registry-labelling-service. Updated automatically when the playground redeploys.

@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 14402a3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@emdash-cms/registry-moderation Patch
@emdash-cms/admin Patch
@emdash-cms/registry-lexicons Patch
@emdash-cms/registry-client Patch
emdash Patch
@emdash-cms/plugin-cli Patch
@emdash-cms/labeler Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 82c1aa9 Jul 12 2026, 09:13 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 82c1aa9 Jul 12 2026, 09:14 PM

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 3,014 lines across 3 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@pkg-pr-new

pkg-pr-new Bot commented Jul 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/admin@1909

@emdash-cms/auth

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth@1909

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth-atproto@1909

@emdash-cms/blocks

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/blocks@1909

@emdash-cms/cloudflare

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/cloudflare@1909

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/contentful-to-portable-text@1909

emdash

npm i https://pkg.pr.new/emdash-cms/emdash@1909

create-emdash

npm i https://pkg.pr.new/emdash-cms/emdash/create-emdash@1909

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/gutenberg-to-portable-text@1909

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-cli@1909

@emdash-cms/plugin-types

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-types@1909

@emdash-cms/registry-client

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-client@1909

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-lexicons@1909

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-moderation@1909

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/sandbox-workerd@1909

@emdash-cms/x402

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/x402@1909

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-ai-moderation@1909

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-atproto@1909

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-audit-log@1909

@emdash-cms/plugin-color

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-color@1909

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-embeds@1909

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-field-kit@1909

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-forms@1909

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-webhook-notifier@1909

commit: 14402a3

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 82c1aa9 Jul 12 2026, 09:14 PM

@github-actions github-actions Bot added the query-count changed PR diff modifies query-count snapshot files label Jul 10, 2026
@ascorbic ascorbic added this to the 1.0 milestone Jul 10, 2026
@ascorbic ascorbic changed the title docs: plan plugin registry labelling service implementation Plugin registry labelling service Jul 10, 2026
@ascorbic

Copy link
Copy Markdown
Collaborator Author

Execution ledger

Sequence Workstream PR Status Gate
01 W0.1 + W0.2 contracts and policy fixtures #1910 Draft, ready for maintainer ratification Gate 0
02 W0.3 vocabulary cutover audit #1912 Draft, ready for audit review; production preflight still required Gate 0
03 W0.4 crypto interoperability #1911 Draft, ready for maintainer ratification Gate 0

Wave 0A coordinator verification:

  • All three branches target feat/plugin-registry-labelling-service and are isolated to their assigned scope.
  • pnpm lint passes with 0 warnings/errors on all three branches.
  • Formatting and git diff --check pass.
  • Contract fixtures: 30/30 expected moderation outcomes reproduced.
  • Vocabulary audit: 19/19 structural-screen cases and 10/10 expiry/quarantine cases pass.
  • Crypto: independent Node vector test 1/1, workerd interoperability 12/12, both targeted typechecks pass.
  • Final independent reviews report no unresolved high/medium findings.

No umbrella checklist item is marked complete until the corresponding draft PR is ratified, merged, and reverified on the integration branch.

@github-actions github-actions Bot removed the query-count changed PR diff modifies query-count snapshot files label Jul 10, 2026
ascorbic added 3 commits July 10, 2026 13:25
* docs: define labeller gate 0 contracts

* docs: record contract ratification
* docs: audit registry label vocabulary

* docs: record vocabulary ratification
* test: prove label crypto interoperability

* test: keep crypto vector with retained tests

* docs: record crypto ratification
@ascorbic

Copy link
Copy Markdown
Collaborator Author

Wave 0A complete

Sequence Workstream PR Result
01 W0.1 + W0.2 contracts and policy fixtures #1910 Ratified and merged
02 W0.3 vocabulary cutover audit #1912 Conditionally ratified and merged; production zero-row preflight remains required before W1.1 rollout selection
03 W0.4 crypto interoperability #1911 P-256 contract ratified and merged

Gate decisions recorded:

  • Public NSIDs/API, ATProto state reduction, issuance rules, cross-labeller policy, overrides, and 30 moderation outcomes are frozen.
  • Canonical security-yanked and collision-safe ingest identity are frozen. No compatibility path is added unless the production preflight finds legacy rows.
  • P-256, canonical-CBOR atcute signing, strict DID key validation, and rotation/compromise ordering are frozen.
  • Retained crypto fixtures live under packages/atproto-test-utils/tests/fixtures; no retained source/test depends on .opencode/plans.

The Cloudflare API connector and local Wrangler OAuth token both returned 10000 Authentication error for EmDash CMS D1, so the production preflight remains an explicit operator action.

@ascorbic ascorbic mentioned this pull request Jul 10, 2026
18 tasks
@ascorbic

Copy link
Copy Markdown
Collaborator Author

Shared label-verification precursor #1952 merged at 2d6ad44. Post-merge integration gate passed on that SHA: full build, full and aggregator typechecks, type-aware lint with 0 findings, 216 aggregator tests, 66 registry-moderation tests, and 29 labeler workerd tests. The shared primitive tracker item now cites #1952; signed subscription ingest remains open.

ascorbic and others added 6 commits July 11, 2026 16:21
- Sweep labeller -> labeler to match the #1926 US-spelling standardization
  of NSIDs, app/package names, and the aggregator labelers table
- Reconcile ReleaseModeration and PublicAssessment shapes with the ratified
  gate-0 contract and the implemented registry-moderation evaluator,
  recording the applicableLabels addition
- Fix gate-0 fixture links to their relocated product paths
- Drop the dangling W0.8 dependency from W9.1 (removed with the gate-zero
  scope narrowing)
- Assign the collision-safe label-history migration to W4.3
Staging and production use custom domains from the start. Production key
generation is a deferred maintainer action with offline custody in Keeper;
staging uses a disposable key so code work never blocks on the ceremony.
Prune the ratified NSID item from the spec's open ratification points.
Publisher notifications send through the Workers send_email binding from
an onboarded emdashcms.com address, so no provider API keys are needed.
The open ratification point narrows to the monitored reconsideration
address.
* feat(registry): ingest signed labels in the aggregator

Adds the per-labeler subscribeLabels ingest path: a LabelIngestDO per
configured labeler maintains the outbound WebSocket, verifies every label
against the resolved #atproto_label key before queue acceptance, and
persists its cursor to ingest_state only after durable enqueue. A new
labels queue consumer writes append-only history and the label_state
projection in one atomic batch.

Replaces the label history identity (src, uri, val, cts) with a
collision-safe digest primary key plus unique (src, source_sequence,
frame_index) coordinates, and adds validated epoch-millisecond columns so
SQL never orders RFC 3339 text. Tables were empty in every deployment
(no writer existed; production preflight confirmed zero rows).

* fix(registry): correct enforcement filter vocabulary and expiry comparison

Adversarial review findings: the search enforcement filter still matched
the legacy 'security:yanked' value (canonical labels use 'security-yanked')
and compared RFC 3339 expiry strings as text, which misorders across
timezone offsets. Both were dormant against the empty label_state table
and would have gone live with the new ingest writer. The filter now
matches the canonical value and compares the validated epoch-millisecond
column. Also bounds the stream client's inbound decode buffer, failing
closed on overflow.

* fix(registry): address ingest review findings

A connection ending on a verification failure no longer counts earlier
frames as progress, so an alternating good/unverifiable stream escalates
backoff instead of hammering. The scheduled wake pump awaits its DO
fetches collectively so waitUntil keeps them alive. Stream listeners
attach before accept() so an immediate frame or close can't slip past.
The label migration refuses to run against a deployment that already has
label rows. The consumer dead-letters any label whose datetimes fail to
parse to instants rather than committing NaN epoch values.
* feat(registry): add accepted-labeler header parsing

Adds parseAcceptLabelersHeader and serializeContentLabelersHeader to the
shared moderation package: RFC 8941 list parsing restricted to the forms
the ATProto label spec uses, strict DID validation, first-occurrence
ordering, union merging of repeated-DID redact flags, and typed syntax
errors. An empty header value means no accepted labelers; resolving a
missing header to deployment defaults stays with the caller.

* feat(registry): resolve accepted-labeler policy at the aggregator boundary

Parses atproto-accept-labelers once per request: a missing header resolves
to the deployment's trusted labelers with redact enabled, an empty header
accepts none, malformed syntax returns InvalidRequest, and requested DIDs
without a labelers row are omitted. The response reports the sources
actually considered via atproto-content-labelers, now correctly CORS-
exposed in place of the request header. Also exports the package-scope
and release-scope hard-block vocabulary from the shared moderation
package so SQL enforcement and the evaluator cannot drift.

* feat(registry): enforce and hydrate labels on aggregator reads

Search and latest-release selection exclude subjects carrying hard-block
labels from the request's accepted sources, with CID-bound labels applying
only to the subject's current CID and package/publisher blocks cascading
to releases. Direct package reads and release lists return content with
hydrated labels; an accepted source with redact enabled turns an active
takedown into a response indistinguishable from absence, including for
the parent package of a release list. Redaction decisions always see the
full label set; the lexicon's 64-label cap applies only on the wire.
Latest-release selection skips the latest_version fast path whenever an
accepted policy is in force, since the pointer is not label-aware.

* docs(registry): clarify label cap boundary and cursor redaction trade-off
* feat(registry): add hydrated moderation evaluation and client adapter

evaluateHydratedReleaseModeration evaluates aggregator-hydrated labels
that carry no signature, sharing one evaluation body with the branded
verified path so the two cannot drift; the branded entrance keeps its
runtime check and remains required for any positive-assessment gate.
The registry client gains a moderation adapter that assembles subject
context from validated views, an accepted-policy resolver preferring the
response's atproto-content-labelers header over configured values, and
response-header exposure on DiscoveryClient. Corrects a latent invalid
CID in the ratified moderation corpus (recorded as a gate-0 amendment)
that structural validation surfaced.

* feat(registry): enforce moderation labels in install, update, and CLI

Install and update gate on the shared moderation evaluation — package and
publisher cascades, CID-bound labels, negation and expiry — immediately
before artifact download, replacing the raw security:yanked string
comparisons. Only actual blocking labels or a redact-flagged takedown
block; the missing-assessment-pass state does not, since the positive-
assessment gate is a later, flag-controlled phase. Update now fetches the
package view (it previously checked release labels only, missing every
cascade) and cross-checks aggregator identity like install. The artifact
proxy refuses media for blocked releases, update-check reports per-plugin
moderation state, and the CLI renders eligibility instead of raw labels.

* fix(registry): block on labels regardless of assessment-state ranking

The evaluator ranks pending/error above automated blocks, so a malware
label with a co-present assessment-pending label yields eligibility
'pending' while blockingLabels still carries the block — and the gate,
keyed on eligibility, failed open. All three consumers now key solely on
blockingLabels/redacted, which still admits missing-assessment-pass and
pure pending/error states.

* fix(registry): fail closed on label-state collisions via one shared predicate

A block label colliding with a same-cts negation puts the stream in the
ratified fail-closed error state with empty blockingLabels, which the
gate — restated separately in three consumers — read as not blocked. The
shared moderation package now exports isModerationBlocking as the single
blocking predicate (applicable blocking label, redact-flagged takedown,
or label-state collision; never keyed on eligibility), and core install/
update, the artifact proxy, and the CLI all delegate to it.
ascorbic added 2 commits July 12, 2026 13:23
* feat(admin): show registry moderation state and gate blocked installs

The registry browser now evaluates moderation labels client-side: blocked
releases stay visible but their install is gated with an explanation
panel, warning-labelled releases surface their warnings in the install
consent dialog, browse cards mark blocked packages, and the plugin
manager flags blocked or warned updates. Each response is evaluated
against the accepted-labeler policy that produced it — the
atproto-content-labelers header travels with the data via a per-call
discovery client. Localized label names come from a descriptor map over
the canonical vocabulary, falling back to the raw value for anything
unknown. Removes the legacy security:yanked string filter that silently
hid yanked releases.

* fix(admin): evaluate package labels against their own response policy

Adversarial review: package-scope labels ride on the package response
while release-scope labels ride on the releases response, each with its
own atproto-content-labelers header. Evaluating both against only the
releases policy could filter out a package/publisher block whose labeler
that response did not report. The two header-derived policies are now
unioned. Adds coverage for the header-precedence path and a package-scope
block surfaced only via the package response header.

* fix(admin): localize the release-holdback duration units

Bot review: formatHoldback returned bare English units. Route them
through Lingui plurals like the rest of the admin.
…#1976)

* feat(registry): add assessment persistence and automated label issuance

Adds the labeler's assessment storage model — subjects, assessments with a
CHECK-constrained lifecycle vocabulary, the current-assessment pointer,
findings, evidence objects, and the ingest/dead-letter tables discovery
will use — with CAS-guarded state transitions and a deterministic runKey
so redelivered triggers converge on one run. Finalization is exposed as a
statement builder whose pointer update re-checks the state CAS inside the
same batch, letting orchestration compose assessment completion and label
issuance into one atomic transaction. The issuance boundary gains an
automated-assessment action whose proposals are validated from the
ratified policy fixture (release-only, CID rules, blocking values only
from critical automated-block finding categories); manual issuance
behavior is unchanged and its suite passes unmodified.

* fix(registry): guard automated negation of manual labels and pointer recency

Adversarial review findings. Automated issuance now refuses to negate a
label whose active stream event was issued by a manual action (§10:
automation cannot retract an action-backed human label) — a defense-in-
depth guard at the issuance chokepoint, independent of the orchestrator's
own candidate set. The current-assessment pointer upsert gains a recency
guard so an older run finalizing after a newer one cannot regress it.
CID presence is asserted unconditionally on the automated path per §20.2
rather than only when a policy rule requires it, and the atomicity test
now fails on a runtime constraint violation so it genuinely proves batch
rollback.

* fix(registry): make the automated-negation guard atomic and harden rule lookup

Bot review. The §10 negation guard now runs as a condition inside the
issuance-action INSERT, not only as a pre-batch SELECT, closing the
read-then-write race with a concurrent manual issuance; gating the action
insert (not the label insert) leaves no orphan action when the guard
fires. Automated-path release-rule selection uses .some() over all release
rules so a duplicate reviewer-only rule can't mask an automated one, and
the policy parser now rejects duplicate subjects outright. The assessment
id pattern is shared from assessment-lifecycle rather than duplicated.

* fix(registry): surface the negation policy violation, not a signing alert

Bot re-review: when the in-batch §10 guard suppresses an automated
negation, the missing issued_labels row was misdiagnosed in postCommit as
a signing-state failure, firing misleading alerts. postCommit now re-runs
the negation guard first so the caller sees the policy violation.

* perf(registry): index the §10 negation guard's stream-head lookup

Bot suggestion: the automated-negation guard's latest-label lookup per
(src, uri, val) had no covering index. Add one so it stays cheap as a
subject's label stream grows.

* test(registry): prove negations enforce release-record and CID checks

The release-record and mandatory-CID checks already precede the negation
early-return; only finding-category/severity is skipped. Lock that in.
* feat(registry): add labeler discovery and assessment orchestration

Independent Jetstream discovery of release records: a per-labeler
discovery DO maintains the subscription (cursor in ingest_state), and the
queue consumer verifies each event's exact URI+CID against the publisher's
signed record before creating a subject and issuing assessment-pending —
a forged or unverifiable event dead-letters and produces no label. The
assessment orchestrator drives pending through the stage pipeline to an
atomic finalization that composes the current-pointer update with label
issuance and negates only this run's own prior automated labels, never a
manually-issued one. Production wiring stops at assessment-pending; the
orchestrator and its stage adapters ship with deterministic stubs
exercised only by tests, enforced by a static production-boundary test.
Scheduled reconciliation flags stuck runs and unassessed subjects.

* fix(registry): retry transient DID resolution, tighten negation provenance

Adversarial review. A thrown DID-document resolution is now a transient
RecordVerificationError the consumer retries, rather than permanently
dead-lettering a release on a directory blip at publish time; a document
that resolves but lacks a PDS or key stays permanent. getNegatableAutomated
Labels computes the active stream head across all issuers, so a val whose
latest event is manual is never returned for automated negation. The
production-boundary test also catches bare side-effect imports. The
finalize() currency-check comment no longer claims a lock it doesn't hold
and documents the in-batch guard / workflow lock required to wire the
orchestrator live in W7/W8.

* fix(registry): tighten the finalization currency window

Re-check subject currency inside finalize() with no signing between the
check and db.batch, shrinking the delete/cancel window from every label's
signing round-trip to two adjacent D1 ops. Full closure still needs the
workflow lock deferred to W7/W8; a mid-run delete injected via a stage
now proves the re-check aborts before issuing any label.

* fix(registry): scope negations to this labeler, verify deletes, emit all blocks

Bot review of #1978. getNegatableAutomatedLabels is scoped to this
labeler's own src (a labeler only negates labels it issued). A delete
event now confirms the record is genuinely absent at the PDS before
suppressing any work — a still-resolving record dead-letters as a
forged/premature delete, matching the distrust the create path already
applies. A PDS stream error after headers is re-wrapped as a transient
PdsVerificationError so it retries instead of dead-lettering. And
resolvePolicyOutcome emits every distinct critical blocking finding, not
just the first.

* fix(registry): dead-letter permanent delete-verification failures

Bot review: the delete path retried permanent verification failures
(INVALID_URI, DID_RESOLUTION_FAILED) instead of dead-lettering them,
diverging from the create path. Both paths now route through one shared
classifier — transient retries, permanent dead-letters and acks — so they
can't drift.

* fix(registry): reactivate tombstoned subjects and break currency ties

Bot review. createSubject's conflict clause now clears deleted_at on a
verified re-observation, so a delete-then-recreate (or an out-of-order
delete before create) correctly reactivates the subject — closing the
create-after-delete race that was previously deferred, since the create
path only runs after PDS verification confirms the record exists.
isSubjectCurrent breaks a same-millisecond observed_at tie by CID so
exactly one subject at a URI is ever current.

* fix(registry): negate assessment-pending on delete, keep fresh error label

Bot review. A delete now negates the assessment-pending label each
cancelled run issued (using the run's own assessment id and a
deterministic idempotency key), so a deleted release stops advertising an
in-progress assessment through queryLabels. And finalize()'s negation pass
keeps a freshly-issued assessment-error, so a second error run for the
same subject can't negate its own new error label.

* fix(registry): retry issuance during signing pause, test error preservation

Bot review. issueAutomatedAssessmentLabel's signing-pause/stale/refresh
conditions now throw a typed LabelIssuanceUnavailableError (identical
messages) that the discovery consumer retries instead of dead-lettering —
so a discovery or delete event arriving mid key-rotation doesn't
permanently lose its label. Adds the two-consecutive-error-runs
regression test for the assessment-error keep-set fix, and a paused-
issuance retry test.

* test(registry): cover paused-issuance retry on the delete path

* docs(registry): document the deferred finalization concurrency gaps

The three finalize() concurrency gaps (stale run leaving its pending
label, unchecked finalization CAS, uncaught transition conflict) are all
closed by the per-subject workflow lock the spec assigns to production
wiring (§14.1, W7/W8). The production-boundary test proves nothing reaches
this method in production until then; documenting them together so the
lock work closes the whole family.
ascorbic and others added 4 commits July 12, 2026 19:08
… machinery

Bundled plugins have no dependency graph; remove all dependency/advisory/CVE/
malware-hash/signature analysis. Full size-capped bundle analyzed in-full by
Kimi K2.7 Code via the AI binding (multimodal: code + images). Real pipeline is
deterministic declared-vs-actual capability analysis + full-bundle AI. Frozen
public output shape (scannerVersions, coverage.dependencies) retained for
contract stability, vestigial in v1.
…dencies

Unpublished service; no dependency/scanner analysis exists, so remove the
fields rather than carry them empty. Lexicon, bindings, and code pruned in the
public-API PR.
ascorbic added 2 commits July 12, 2026 22:10
* feat(labeler): normalized finding contract (W8.1)

Add the canonical NormalizedFinding shape every analysis stage produces,
with validateFinding/validateFindings enforcing it against the
policy-derived allowed-category set (automated-block union warning) and a
run's recorded evidence objects before an assessment finalizes.

toPublicFindingView routes through evidence.ts's never-typed
PrivateFindingRecord -> PublicFindingView narrowing so private fields
can't leak. The orchestrator now validates collected findings before
resolving policy outcome; an invalid finding aborts the run as a
non-retryable stage-adapter bug.

* fix(labeler): copy validated finding arrays to decouple from caller input

requireStringArray returned the caller's array by reference, so mutating
the input array after validation would mutate the validated finding.
Return a copy so the finding matches the contract's immutability claim.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants