Repository navigation
Conversation
…(kits K1 slice 1) A kit version is the exact canonical bytes of its normalized manifest, keyed by its kitDigest (@pcc/spec capability-kit.ts, #397). - Authority lives in write-once files on the durable volume: - manifests/: through FsRegistrySnapshotStore; - publications/: one record per digest, created exclusively (link), so the first publisher wins. It is NOT a capability_template_store row: on master any key can rewrite template rows (kits D5), and #395's fix waits on WP-A. - Every read re-verifies the bytes against the digest, the canonical form and the publication record, and fails closed (500 kit_integrity_failure). The index is a cache rebuilt from the files at first use; an entry that fails verification is skipped, never listed. - Routes: - GET /api/kits (filters); - GET /api/kits/:digest; - POST /api/kits (authenticated; complete kits only; a 24h per-publisher cap; 503 in production without a durable root); - POST /api/kits/:digest/fork (lineage checked). Scope defaults match template authoring. Audit events on publish and fork. - The publisher is never in a response. Deprecation (publisher ownership, after WP-A), S4 ranking and the tool catalog as a view over kits are slice 2. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
…K1 slice 1) Adds service-unit and Fastify app.inject route tests for KitRegistry and kitRoutes covering publish/read round-trip, integrity (bit-flip, non-canonical bytes, mismatched/missing publication records), same-instance and cross-instance publish concurrency, validation (invalid manifest, 413 size bound, incomplete kit, unknown/mismatched parent, invalid digest), auth, publisher privacy, the rolling 24h publish quota, the durability guard (including isDurableKitRoot env-matrix), list filters/ordering/pagination, registry reload, and audit log entries. Also adds scope-checker coverage for the new POST /api/kits and POST /api/kits/** default scope requirements. All 50 new tests pass against the current implementation; no spec deviations were asserted away. Agent: test-writer-kilo Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
…r costs quota (K1 own review) My mutation pass over test-writer-kilo's black-box suite found four survivors. - Read path: the digest recompute was implied by the sha256 check plus the canonical-form check, so each made the other an equivalent mutant. It is removed. A new test stores another kit's valid canonical manifest under a digest, so the hash check is required on its own. - Re-publish: re-publishing an existing digest must never cost the re-publisher quota. A new test has a publisher at their cap re-publish another's kit (200, publisher unchanged). - The route's principal check stays as defense in depth. It is equivalent: the service refuses a missing principal itself. Mutations 14/15 killed (the survivor is that equivalent route check). Gateway 208 files, 3758 passed / 13 skipped; gateway tsc 0; the kits test files typecheck clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
…e; the files are the only truth Every finding was reproduced at 47e6eec first. HIGH 1 + HIGH 2 (authorization): - scope-checker replaces its defaults with the governance table whenever that table has rows, so the kit rules could be absent and a contributor:read key published; - it also skips callers without an API key, so a SIWE session published. The kit routes now decide the role themselves: an API key holding template_author, operator, admin or *. A session carries no scopes and is refused (403 kit_publish_requires_api_key). getCallerScopes is exported for this, and is injectable in kitRoutes for tests. MEDIUM (storage; services/kit-registry.ts rewritten): - No cache. list(), get(), the existence check and the quota read the files every time, so a file that stops verifying stops being listed and another process's publish is seen at once. - No symlinks: every file opens with O_NOFOLLOW and every registry directory is lstat-checked. The configured root may be a mount symlink, resolved once. - A hard-link probe runs before the first write, so a volume without links gets 503 registry_unsupported_fs and no orphan manifest. - An exact cross-process quota without a lock: claim n (an exclusive create under quota/<sha256(publisher)>/) needs claim n - limit to be at least 24h old; a malformed claim counts as recent. - Manifests now use the same exclusive-link write as records (a second writer must carry identical bytes). Gateway 208 files, 3774 passed / 13 skipped; gateway tsc 0; the kits test files typecheck clean; mutations 20/20 killed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
…fail closed without O_NOFOLLOW astra k1b (SHIP-WITH-FIXES) kept root confinement open on three points, each checked at fe74533 first: (a) REPRODUCED. scan() read <root>/publications before checking it, so a symlinked area was listed (nothing was served from it). The area is now lstat-checked first; a symlink or non-directory is a KitIntegrityError. (b) NOT REPRODUCED. dirFor creates and lstat-checks one path component at a time. With <root>/manifests or <root>/quota symlinked to an empty outside directory, publish fails and the outside directory stays empty. No change; the case is now a test. (c) REPRODUCED by trace: `O_NOFOLLOW ?? 0` silently followed symlinks on a runtime without the flag. Now the capability probe refuses to publish (503 registry_unsupported_fs) and reads fail closed (KitIntegrityError). noFollowFlag is injectable for tests. Gateway 208 files, 3778 passed / 13 skipped; tsc 0; kits test files clean; mutations 23/23 killed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
Comment-only. The publishRefusal doc comment in routes/kits.ts and the section comment above the route-only tests in kits-scope.test.ts now say that the kit routes decide the publishing role themselves, so the check holds for any deployment configuration. No code, test name or assertion changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A
LamaSu
had a problem deploying
to
trusted-checks
October 7, 2026 00:08 — with
GitHub Actions
Failure
LamaSu
had a problem deploying
to
trusted-checks
October 7, 2026 00:09 — with
GitHub Actions
Failure
This branch had an error being deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Kits K1 slice 1 is the durable Capability Kit registry (technical section 7, MUST-CLOSE 1-2; ledger row 6, "no in-memory-only production catalog").
Stacked on #397 (SHIP 112e, MERGE-READY). It uses #397's
CapabilityKitManifestV1,normalizeKitManifest,computeKitDigestandvalidateKitCompleteness. Merge after #397.A kit version is the exact canonical bytes of its normalized manifest, keyed by
kitDigest = sha256(those bytes). Who published it and when is server metadata, never part of the hashed bytes.Storage: write-once files on the durable volume
<root>/manifests/: the manifest bytes, through the existingFsRegistrySnapshotStore(write-once, collision-verified).<root>/publications/: one publication record per digest,{schema, kitDigest, publisher, publishedAt}. It is created EXCLUSIVELY (a hard link from a temp file), so the first publisher wins even across processes, and a record is never overwritten.PCC_KIT_REGISTRY_DIR, else$RAILWAY_VOLUME_MOUNT_PATH/kit-registry, else./data/kit-registry. In production with neither variable set, publish answers 503registry_not_durable.capability_template_store: on master any key can rewrite template rows through the generic template routes (kits D5). fix(gateway): bind job-offer claims/events, tool-catalog entries and templates to their authenticated owners (kits K0 slice 2; merge after WP-A) #395's fix for that waits on WP-A, and a kit row there would also surface in the template list, fork and rate routes. No schema change.Integrity: verified on every read, fail closed
GET /api/kits/:digestre-reads the bytes and requires all of these:Anything else answers 500
kit_integrity_failure, and the bytes are not served. The listing is a cache rebuilt from the files at first use; an entry that fails verification is skipped, never listed.Routes
GET /api/kitscsdUrl,deviceFamily,interface,q;limit(max 100) andoffset; newest firstGET /api/kits/:digest{kit: {kitDigest, manifest, publishedAt}}POST /api/kitsincomplete_kit); parent must exist (422unknown_parent); 256 KiB cap (413); at mostPCC_KIT_PUBLISH_DAILY_LIMIT(default 20) new kits per publisher per 24 h (429); re-publishing a digest returns the existing publication (200,created:false)POST /api/kits/:digest/forkparentKitDigestmust equal:digest(400parent_mismatch; 404 if absent)POST /api/kitsandPOST /api/kits/**requiretemplate_author,operatororadmin, like template authoring. Self-service*keys pass, as everywhere. Deployments that customise endpoint scopes need rows for these two routes.kit.publishedandkit.forkedevents (actor,capability-kit, digest).Not in this slice
Tests
packages/gateway/src/__tests__/kits-registry.test.ts(44) andkits-scope.test.ts(7) were written black-box from the spec by a Sonnet test writer, then reviewed and extended by me. They cover:isDurableKitRootenvironment matrix;tsc --noEmit(gateway)Round 2: astra k1-511 fixes, at fe74533
All 7 findings were reproduced at 47e6eec first.
template_author,operator,adminor*, read withgetCallerScopes. This holds whatever the governance table holds. A SIWE session is refused (403kit_publish_requires_api_key) until sessions have a role model.O_NOFOLLOWand every registry directory islstat-checked. The configured root may be a mount symlink, resolved once.quota/<sha256(publisher)>/is an exclusive create, and needs claim n − limit to be at least 24 h old.Evidence: gateway 208 files, 3774 passed / 13 skipped; tsc 0; mutations 20/20; secret scan 0. Review pack:
k1b-511-kits-registry-r2-fe745332.🤖 Generated with Claude Code
https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A