Repository navigation
fix(service-package,cloud-connection): convert stored package bodies forward at the sys_packages and marketplace doors - #24028
Conversation
… the sys_packages and marketplace doors Claude-Session: https://claude.ai/code/session_013JuCowY7RrGXYbGEv3cWsQ Co-authored-by: Claude <noreply@anthropic.com>
…n over the real engine and sqlite Claude-Session: https://claude.ai/code/session_013JuCowY7RrGXYbGEv3cWsQ Co-authored-by: Claude <noreply@anthropic.com>
…are one converted body Claude-Session: https://claude.ai/code/session_013JuCowY7RrGXYbGEv3cWsQ Co-authored-by: Claude <noreply@anthropic.com>
… under ADR-0087 D2, pinned over the real registry Claude-Session: https://claude.ai/code/session_013JuCowY7RrGXYbGEv3cWsQ Co-authored-by: Claude <noreply@anthropic.com>
… package bodies forward Claude-Session: https://claude.ai/code/session_013JuCowY7RrGXYbGEv3cWsQ Co-authored-by: Claude <noreply@anthropic.com>
…ckage-doors-convert-forward
📓 Docs Drift CheckThis PR changes 3 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin eac82e1b6bfd79361bd0afda1543f6091e25805f && git checkout eac82e1b6bfd79361bd0afda1543f6091e25805f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e246f06119e9c967d5e4ef3e56e74b6312ce2525 bb3838c8153b74cf54ebdb33c1b0665376851190 && git checkout -B drift-repro e246f06119e9c967d5e4ef3e56e74b6312ce2525 && git merge --no-ff bb3838c8153b74cf54ebdb33c1b0665376851190
node scripts/docs-audit/affected-docs.mjs --json e246f06119e9c967d5e4ef3e56e74b6312ce2525
|
… another package's fixture Its glob-shape rule read packages/objectql/src/__fixtures__/hotcrm-4.0.1-published-pages.artifact.json, declared in scripts/cross-package-test-inputs.mjs, as a reference to the metadata artifact. Only that fully spelled path is set aside; a bare glob on the same line, a climbing path, a wildcard package path and the metadata fixtures directory still count, each pinned in --self-test. Claude-Session: https://claude.ai/code/session_013JuCowY7RrGXYbGEv3cWsQ Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read at 2026-10-16T20:33Z, against the head named above. Inputs: card #23974's body and all four comments ( ① Derived judgmentsDoor 1:
Door 2:
Door 3: the marketplace install (
The pins
Premise correction: true.
Is the restamp honest?
What this means for the card's reach claim
Surface supplements outside the claim
The gate edit: a precision fix in intent, a weakening as written (
② Semver level
③ Boundary flags
Check-runs on the head: 41 in all, every one completed: 36 success and 5 skipped. The skipped five are FAIL reasons
Owed in the same revision (not blocking)
Implemented-by: VERDICT: FAIL Generated by Claude Code |
席位核对 · REWORK (patch round 1) ·
|
…te's carve-out Corrects f1443d9, whose carve-out let two spellings through and whose docblock said otherwise: a path into packages/metadata/src/__fixtures__/ spelled with a '.' segment was set aside because the raw match was compared, and a match that ended at a glob character or template interpolation was deleted as its glob-free prefix. Rule 3 now sets aside only a whole path token (delimited by a quote, backtick, whitespace, comma, bracket, parenthesis or end of line) that has no '..' segment, is outside the metadata fixtures directory after path.posix.normalize, and is one literal file path with no glob character or '${'. The four spellings the contract review named are self-test cases, with a '.'-segment literal into the guarded directory and a '.'-segment path to another package; the matcher battery floor is 20. Claude-Session: https://claude.ai/code/session_013JuCowY7RrGXYbGEv3cWsQ Co-authored-by: Claude <noreply@anthropic.com>
os-dev addendum · patch round 1 (REWORK 6224211194, R1 of 6224173098) ·
|
Contract reviewServed-tier: Read at 2026-10-16T21:30Z, against the head named above. This is a same-form re-review after patch round 1 of record Inputs:
Code was read with The probe. I copied
Probe strings and results:
① Derived judgmentsR1: cured.
What the probe did not keep: five strings, none a FAIL reason. The answers on
Anything set aside wrongly, or newly flagged?
The self-test (
The docblock (
Two precisions, neither one false:
The commit message. The round touched one file.
Carried from
② Semver level
③ Boundary flags
Check-runs on the head.
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 · ACCEPT ·
|
|
Merge conflict · PR #24028 is accepted (
The likely sources on Next: the dev merges Generated by Claude Code |
…ckage-doors-convert-forward # Conflicts: # packages/cloud-connection/src/marketplace-install-local-plugin.ts # packages/metadata-protocol/src/protocol.ts # packages/services/service-package/src/index.ts
… origin main's registry rows record After merging main, the registry row carries the caller's origin. The marketplace pin's manifest wrapper now forwards register's options, and both door pins assert that the converted body still lands as 'authored' (sys_packages) or 'marketplace' (install, rehydrate). Claude-Session: https://claude.ai/code/session_013JuCowY7RrGXYbGEv3cWsQ Co-authored-by: Claude <noreply@anthropic.com>
os-dev addendum · merge of
|
|
Merge read · At head
No door's behaviour changed relative to contract record Generated by Claude Code |
Fixes #23974
Clause-②: yes (widening)
Three package doors called the registry without going through the ADR-0087 versioned window that PR #23962 opened at the registration door. This PR routes each door through that window, or records why the door stays strict. The conversion entry point
ObjectQL.convertPackageBodyForwardis reused as it stands. Nothing changes inpackages/objectql/**,packages/metadata-core/**orpackages/spec/**, andSchemaRegistry.installPackagegets no second judge.Premise check, on
origin/main(base92fd343, merged up tofd9664ffa)service-package/src/index.ts:715calledregistry.installPackage(rec.manifest), and onetry/catchwrapped the whole loop. That catch logged anything other than a seam failure atdebug. So the first record the registry refused also dropped every record after it, and nothing abovedebugsaid so.protocol.ts:31393). Its premise as a conversion door does not, see door 2 below.:780/:790/:795/:827) and the install path (:1554/:1618/:1625) both handregisterthe raw body and hand the same raw body tobindArtifactHandlers/applySideEffects.engines.protocol: ^17.7.0. This runtime is protocol 18, so the ADR-0087 D1 handshake at doors 1 and 3 (andassertProtocolCompatat door 2) refuses that body by name before the registry or any conversion sees it. Door 1continues past it, so a HotCRM-4.0.1 row alone does not stop hydration on v18 (pinned below). The window at these doors opens for a stored body whose range admits major 18 and whose floor predates a retirement, for example>=17.7.0. The converting pins restamp the fixture's body that way: same 17.7.0 floor, same pages. The loop defect is real for any registry refusal and is fixed below.Door 1:
sys_packagesboot hydration (@objectstack/service-package)objectql.convertPackageBodyForward(reached through the engine the plugin already holds, asregistryis) beforeregistry.installPackagejudges it. The stored row is not rewritten, so it converts again at every boot. This follows the ADR-0087 addendum: metadata at rest replays the chain at every load.error, naming the package id, carrying the refusal's message, and with the refusal attached. The line also states the consequence (absent from the registry for this process while its row remains) and the fix. The records after it keep hydrating.isSeamUnreadableWARN is untouched.Hydrated N package(s) from sys_packages into registrynow counts exactly the records the registry accepted in that pass. It excludes records the D1 handshake refused, records already registered, and records the registry refused (each of those is reported by name).Door 2:
protocol.installPackage/POST /api/v1/packagesstays STRICT (authoring-side)The decision comes from what each caller actually sends. It is recorded at the call site in
protocol.ts, and the code is unchanged.POST /api/v1/packages(packages/runtime/src/domains/packages.ts) parses the whole body throughPackageInstallBodySchema/ the closedManifestSchemabefore calling the primitive. Measured on this tree: the HotCRM body is refused there withUnrecognized key(s) on this package manifest: pagesin all three cases: as published, with only the convertible shapes, and with the conversion applied by hand. No package body that carries metadata collections reaches the primitive over HTTP, so a conversion inside it would never run for this door.duplicatePackage(:27496) copiessrcPkg.manifest, the registry's own record. The registry holds only what it accepted (already converted at whichever door installed it), so the duplicate sees the converted body. It behaves the same either way.:27870):plan.baseManifestis identity keys plusid/name/namespace(package-unmanaged-install.ts:473), with no collections. The window has nothing to act on.6219354007) call the primitive in-process with a manifest they are authoring now. A retired declaration there is the author's to fix, refused loudly at the moment of writing.sys_packagesstores: unchanged. The primitive refuses before it persists, so a refused body writes no row. A body that lands insys_packagesby any road is data at rest from then on, and door 1 converts it at every boot.protocol.installPackageis refused withMisplacedDefaultPageError(INVALID_METADATA, 422), with no registry record, nosys_packagesrow and no conversion warning.Door 3: the marketplace install's side readers (
@objectstack/cloud-connection)convertPackageBodyForward(ctx, manifest)that delegates to the engine's entry point). That one object is whatmanifest.register,bindArtifactHandlers,applySideEffectsand the rehydrate's seed read (maybeHealSampleData, which now takes the converted body) receive. The ledger keeps the body as installed.registermeets the entry point a second time. The already-converted body comes back with no second warning, and the pin asserts exactly one warning per conversion on both paths.Pins (real
SchemaRegistrythroughout; a stub cannot refuse)service-package/src/hydration-convert-forward.test.ts: real stack (ObjectKernel+ObjectQLPlugin+SqlDriverbetter-sqlite3 on disk +PackageServicePlugin). One process publishes through thepackageservice; a second process on the same file hydrates, as a restart does. I chose the real stack because the defect sits between the store, the engine's converter and the registry's refusal, and a unit double can show none of the three. Cases:error, withcode/statusasserted on the attached refusal) followed by a record that still hydrates;cloud-connection/src/marketplace-install-local-convert-forward.test.ts: a realObjectKernel+ObjectQLPluginsupplies the converter and themanifestservice. On rehydrate and install, all readers receive the same converted object. CONTROL on both paths: a range-less body is refused atregister(install:422 PLUGIN_REGISTER_FAILED; rehydrate: the refusal is logged for that entry withcode/status), and no side reader runs.Ablations (
scripts/ablation-replace.mjs; each restore proven blob == HEAD andgit diff HEADempty)rec.manifestunconvertedhydrates … converted,keeps hydrating)keeps hydrating, CONTROL: the refusal falls to the olddebugline)entry.manifestrehydrate — … same converted body)applySideEffectsreads the rawmanifestinstall — … same converted body)The tests import the touched files from
src/(and@objectstack/objectql/core/driver-sql/metadata-protocolthrough anchored vitest aliases tosrc/), so nodist/leg applies.Verification, on head
fe7b944bbThe later heads' readings are in the addenda and the dev's hand-backs:
f1443d9bca(the gate fix) andcbec0eac83(patch round 1, addendum6224822850). Atcbec0eac83,dispatch-gates --commandsderives 102, 101 exit 0, and 1 is NOT MEASURED (check:dual-build-cjs-loads, exit 3). The name-fold gate's--self-testpasses 30/30 with a matcher-battery floor of 20. Fullpnpm lintexits 0. The readings below are the first report's.@objectstack/service-package: test 118/118 (8 files), typecheck exit 0.@objectstack/cloud-connection: test 559/559 (47 files), typecheck exit 0.@objectstack/metadata-protocol(a comment-only change): typecheck exit 0, plusdurable-package.test.ts10/10. Its full suite is left to CI, a declared narrowing.--listFilesconfirms that both new test files are in their package's tsc program.pnpm lint(full,eslint . --no-inline-config): exit 0.dispatch-gates --commands: 98 derived, 97 run with exit 0. One is NOT MEASURED:pnpm check:dual-build-cjs-loadsexit 3 (PREREQUISITE NOT MET, it needs every package'sdist/), and CI runs it.check-plugin-teardown-shape.mjs --self-testfirst exited 3 on the shallow clone; after fetching its pinned fixture commit it exited 0.--ranreconciliation: 98 accounted, 97 run, 1 NOT-MEASURED derived from the recorded exit 3.Wiring the pins needed
service-packagedevDependencies@objectstack/objectql,@objectstack/driver-sql,@objectstack/metadata-protocol(no cycle;pnpm-lock.yaml+9). Itsvitest.config.tsgains anchored aliases to those packages'src/(check:test-source-aliasgreen, no ledger widening).Both new tests read the one HotCRM fixture under
packages/objectql/src/__fixtures__/, so it is declared inscripts/cross-package-test-inputs.mjsandturbo.json(check:cross-package-test-inputs,check:ci-filter-paritygreen;corealready covers the path).A surface supplement outside the claim:
scripts/check-position-name-fold-loaders.mjs. Its glob-shape rule 3 read the newpackages/objectql/src/__fixtures__/hotcrm-4.0.1-published-pages.artifact.jsonline inscripts/cross-package-test-inputs.mjsas a reference to the metadata permissions artifact, a false positive. Rule 3 now judges whole path tokens. A token is set aside only when all of these hold:,,],)or end of line;..segment;path.posix.normalized form is outsidepackages/metadata/src/__fixtures__/;${.Everything else on the line is still judged. The self-test passes 30/30, with the matcher-battery floor at 20. Ablating the normalisation reds one case, and so does ablating the token boundary. The first carve-out (
f1443d9bca) leaked./and partial-glob spellings (contract record6224173098, R1), and patch round 1 (cbec0eac83) closed that (record6225074650).Acceptance notes
marketplace-install-local-plugin.ts, outside the claimed region. These belong to the same family as door 3. Conversions DO rewrite translations, jobs and hooks today (packages/spec/src/conversions/registry.ts), though none rewrites seeddata.seedDatasetsOf(entry.manifest)) and the install-timeunrunnableCode(ctx, manifest, …)read nothing a conversion touches, so neither gives a wrong answer now.applySideEffects(ctx, entry.manifest, … door: 'reseed')) reloads RAW translations, including a per-appsettingsgroup thattranslation-per-app-settings-removedstrips. Carrier: cloud-connection: the marketplace reseed door handsapplySideEffectsthe raw ledger entry, so it reloads translations a conversion strips (a pre-17.4 per-appsettingsgroup) that install and rehydrate load converted #24040.duplicatePackage'scatch {}around itsinstallPackagecall swallows a refusal (pre-existing, unchanged here). Carrier: metadata-protocol:duplicatePackageswallows its owninstallPackagerefusal in a barecatch {}— a duplicate whose manifest the registry refuses is created with no package row and no log line #24041.checkProtocolCompatreads^17.7.0 || ^18.0.0asincompatibleon this runtime (measured with a direct call), although the range admits major 18 and the handshake's own comment says an unparsed range never causes a false rejection. Reach through a public door is not measured. Carrier: metadata-core:rangeAdmitsMajorreads an||union as a caret on its first alternative, soengines.protocol: '^17.7.0 || ^18.0.0'is refused as incompatible on protocol 18 — a false rejection the handshake promises never to make #24029.POST /api/v1/packages(registry.installPackageafterassertProtocolCompat) is the same authoring door as door 2 and stays strict too.Seat edit ·
domain:servicesseat 2 ·session_018VqJPGA1NDNSCZsnecsChP· 2026-10-16T21:32Z: the gate edit is declared under "Wiring the pins needed" (N2), the raw-ledger-reader note is corrected and its carriers named (N1, contract record6224173098), and a pointer to the later heads' readings is added under Verification.Generated by Claude Code