Uh oh!
There was an error while loading. Please reload this page.
fix(runtime): stop the package-publish door disclosing driver text on seedApplied - #8518
Conversation
… seedApplied The route-level seed apply that runs for protocols which do not self-apply seeds is a second copy of metadata-protocol's applySeedBodies, and carried the same ADR-0112 defect #8333's P9 fixed there: caught driver text interpolated onto a client-facing payload. seedApplied rides on a 200 publish response as data, so no HTTP boundary's 5xx message withhold can reach it. Reproduced first, through HttpDispatcher.handlePackages with a real SeedLoaderService. The reproduction found two carriers, not one: the door's catch (a driver failure under the loader's dependency-graph read) and the per-read errors[] entries (a driver failure on the seed body read-back, which is the carrier a sys_metadata outage reaches first). The rule is imported from the producer rather than restated: metadata-protocol now exports clientFacingFailureText and seedRequestValidationError (enabling export only, no behaviour change there). The seed request parse becomes a safeParse whose rejection is minted as a real 422, so a malformed seed body is still quoted back to its author while undeclared driver text is withheld. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P7vaLs7bhBPi9m3JyzkhDj
…the changeset Both predictions held: 4 failed | 2 passed with the door reverted, and 3 failed | 3 passed under the over-broad variant (nothing ever quoted), the three being the two authoring quotes and the declared-refusal guard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P7vaLs7bhBPi9m3JyzkhDj
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
…time-seed-apply-disclosure # Conflicts: # packages/metadata-protocol/src/index.ts
📓 Docs Drift CheckThis PR changes 2 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8443
The runtime package-publish door keeps a route-level seed apply for protocols that do not apply seeds inside
publishPackageDraftsthemselves. That fallback is a second copy ofmetadata-protocol'sapplySeedBodies, and it kept the ADR-0112 defect PR #8436 fixed there (#8333's P9): a caught error's sentence interpolated onto a client-facing payload.Reproduced first — the card was read-from-source, and reproducing changed the answer
Driven through
HttpDispatcher.handlePackageswith a realSeedLoaderService(only the engine, the metadata service and the protocol doubled), before any implementation change. Recorded pre-change response — HTTP 200:{ "success": true, "data": { "success": true, "publishedCount": 1, "failedCount": 0, "published": [{ "type": "seed", "name": "project_seed", "version": "h" }], "failed": [], "seedApplied": { "success": false, "error": "SQLITE_ERROR: no such table: sys_metadata" } } }Positive control, same harness, healthy engine:
seedApplied: { success: true, inserted: 2 }with one real bulkinsert('project', [Apollo, Gemini])at the engine — so the injection perturbs a path that genuinely runs, and "no driver text" can never be confused with "nothing happened".The premise holds, and the reproduction added something the card did not have. Four populations reach that field; measured, pre-change:
seedAppliedmetadata.getObjectthrows — the loader's dependency-graph read, unguarded inresolveObjectDefinitionerror: "SQLITE_ERROR: no such table: sys_metadata"protocol.getMetaItemthrows — the seed body read-backerrors: ["read project_seed: SQLITE_ERROR: no such table: sys_metadata", …]error:a multi-line JSON dump of raw zod internalsC is a second carrier on the same field, and the card named only B. It is also the one a
sys_metadataoutage reaches first: the read-back happens before the loader is ever constructed. A fix confined to the door'scatchwould have left the commonest outage shape disclosing exactly as before, while the PR claimed the door was fixed. Both are converted here.The helpers question, settled producer-side
metadata-protocolnow exportsclientFacingFailureTextandseedRequestValidationError; runtime imports them. Enabling export only — no behaviour inmetadata-protocolchanges, and its suite is untouched and green.Why exporting beats restating, on the merits rather than by preference:
isWritablePackagefrom this package with the identical argument written above it: "this defect existed precisely because the lifecycle routes had no copy of it, and a second copy would be the next place it drifts."recordNotFoundErroris the standing precedent for exporting an envelope so a runtime fallback builds the same one.seedRequestValidationErrormints for the same rejection, so a future local restatement in runtime goes red on the first edit to either side.declaresClientRefusalis deliberately not exported: nothing outside its file needs the raw predicate, and an export with no consumer is declared-but-unpulled surface.Authoring feedback is preserved, not blanked — the same cure #8333 used
Population D is why the rule could not simply be applied. A raw
ZodErrordeclares nothing, so the withhold would have replaced a real authoring error withseed apply failed. The cure is the producer-side one, not a loosened collector: the seed request is parsed withsafeParseand its rejection minted as a declaredINVALID_METADATA/ 422, so it satisfies the positive list on its own merits. The author is strictly better off — a curated summary naming the seed and the key, instead of a dump of zod internals.Reverse verification — direction predicted BEFORE the run
Predicted, with
domains/packages.tsreverted toorigin/mainand the export kept: RED for the two withholds (B, C) and the two authoring-envelope cases (D) = 4 failed | 2 passed; green-in-both-directions for the positive control and the declared-refusal[GUARD](E).Measured: 4 failed | 2 passed — as predicted, and each red failed on the text, not on a vague change:
Second variant, which is what makes the guard load-bearing: with
clientFacingFailureTextforced to withhold unconditionally (a blanket blank), predicted 3 failed | 3 passed — section 2's two quotes and section 3's guard. Measured 3 failed | 3 passed, those exact three. Without them the file would be satisfied by "withhold everything", which deletes the self-correcting refusals #4277 exists for.No missed prediction on this card.
File surface
packages/runtime/src/domains/packages.ts— the two conversions plusparsetosafeParse, and the withheld originals now go to the log.packages/runtime/src/domains/packages-seed-apply-disclosure.test.ts— new; 6 cases across positive control / withhold / authoring quote / over-block guard. Every case asserts the contents of the field inside the 200 body; none asserts a status code alone and none uses a baretoThrow()(this door does not throw, it reports).packages/metadata-protocol/src/protocol.ts,src/index.ts— enabling export only, no behaviour change (flagged for the metadata lane)..changeset/runtime-seed-apply-driver-text.md.Nothing outside those two packages.
Verification
Numbers below are the post-merge run:
mainmoved 14 commits under this branch, including two that touchpackages/metadata-protocol, so everything was re-run after merging and rebuilding rather than reported from the pre-merge lap. The one conflict was inmetadata-protocol/src/index.ts— two export blocks added at the same line, both kept.pnpm --filter @objectstack/runtime test— 152 files / 2325 tests passed.pnpm --filter @objectstack/metadata-protocol test— 82 files / 1223 tests passed.pnpm --filter @objectstack/runtime typecheck— clean.pnpm --filter @objectstack/spec check:generated— "All 13 generated artifacts are up to date" (main movedpackages/spec; nothing here regenerates).scripts/pm/dispatch-gates.mjs) and run green:check:nul-bytes,check:changeset-gate-self-tests,check:cross-package-test-inputs,check:durability-log-level,check:engine-double-contract,check:filter-alias-parity,check:objectui-changeset,check:route-envelope,check:query-options-erasure,check:error-code-casing,check:test-source-alias,check:type-check-coverage.check:type-check-debton a full built closure (70/70 turbo build tasks): green — "33 ledger entries re-measured, 1969 raw errors, none above its recorded number; surplus: none". The ledger was not raised.Out of scope, filed not fixed
#8516 — the same handler's
unhideErrorandrebindErrorfields put caught text on that same 200 body from two different producers (the ADR-0045 visibility flip and themetadata:reloadedannounce). Read from source, not reproduced, so it gets its own card with the same reproduce-first condition rather than riding along unmeasured here.Generated by Claude Code