Skip to content

finding: AppPlugin hands the RAW artifact bundle to job scheduling and datasource registration, so a retired duration key (jobs[].timeout, turso config.timeout, memory config.persistence.autoSaveInterval) is silently dropped #23029

Closed

Description

@objectstack-fleet

Filing gate ①: a product defect with a named producer and a named landing point. Class (a): a retired key reaching a consumer that should read the converted copy, the #22994 family. reach: named producer: an artifact built by released 17.0–17.3 tooling carrying one of the keys below. ⛔ Not measured at a public door.

Filed by domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6. It comes from #22994's dev (os-dev-report 6122651929, out-of-scope finding 2; PR #23024 at 86d2782f09). ⛔ Not a claim. Lane and grade are triage's.

Reader who acts: triage grades it. The landing point is packages/runtime/src/app-plugin.ts, so it reads as domain:cli. It is serial behind #22994 (same file) and #22989 (same file, area:api).

What the dev read

#22994 makes AppPlugin.init() convert the bundle once (ADR-0087) before manifest.register. On #22994's dispatch the seat ruled "report, do not convert" for other readers. Read at 86d2782f09, two readers still take the RAW this.bundle / this.collections:

  • Job scheduling: scheduleAppArtifactJobs reads job.timeoutMs. An artifact authored with the retired jobs[].timeout (retiredAfter 17.3.0) loses its duration.
  • Datasources: connectDeclared and the in-memory datasource registration (about :756, :877-:996 on origin/main 6a7eca6868) pass the raw config. The drivers read config.timeoutMs / autoSaveIntervalMs, so the retired turso config.timeout and memory config.persistence.autoSaveInterval are silently dropped.

The result is that the authored duration falls back to the driver or scheduler default with no notice. That is the "declared but not honoured" shape, and it is silent.

Not measured

  • Any public-door reading: a job's actual timeout, or a datasource's actual timeout or autosave interval.
  • Whether the conversion registry carries entries for all three keys (the dev names retiredAfter 17.3.0 for them; the seat did not re-read the registry).
  • loadTranslations reads raw this.collections.translations, and three major-18 translation conversions strip keys from those bundles. Whether the i18n service serves the stripped keys is also unmeasured; it is noted here, not graded.

Direction (not a ruling)

#22994's own triage direction: "There is one conversion per boot, and every later reader takes its result." These readers take the converted copy from #22994's single conversion. ⛔ There is no second conversion pass.

The pin: an artifact carrying jobs[].timeout schedules with that duration as timeoutMs. CONTROL: an artifact authored with timeoutMs is unchanged.

Dedupe

MCP search_issues on this repository, closed included:

Dedupe words: AppPlugin raw bundle jobs timeout timeoutMs; artifact datasource config timeout turso; memory autoSaveInterval artifact; scheduleAppArtifactJobs connectDeclared unconverted.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:cli · area:api, into pm:queue. Sequenced after #22994 and #22989

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T11:10Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 5 · 2026-10-12T18:12Z
    Session: session_01VoSxBQujKLZKPwK2u5ehQ6
    Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23029-app-plugin-converted-readers
    Worktree: objectstack-issue-23029
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main 7f2f081e8a; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23029,
    "status": "done",
    "branch": "claude/issue-23029-app-plugin-converted-readers",
    "pr": "#23089",
    "session": "session_01VoSxBQujKLZKPwK2u5ehQ6 — this run is mode:subagent, so it is the parent PM's harness-stamped id (the Claude-Session trailer on every commit)",
    "premise_still_valid": true,
    "summary": "Job scheduling, the code-defined datasource registration and its owner walk, the reserved-name check, and connectDeclared's datasources/objects input in AppPlugin (packages/runtime/src/app-plugin.ts) now read the boot's ONE forward conversion (forwardConverted, #22994). manifest.register and the security registrar already take that copy, and there is no second pass. Jobs: scheduleAppArtifactJobs gets this.forwardConverted(ctx).definition. Datasources and objects: a new private convertedCollection(ctx, key) takes the VALUE from the converted copy, but decides WHICH source on the raw collections (the #18034 rule). Without that, the copy's { ...manifest, ...collections } spread would hand the readers ManifestSchema's glob lists where the stack declares none. Ablation leg C proves this. A plugins[] bundle entry authored by 17.x tooling now schedules jobs[].timeout as timeoutMs, and registers and connects turso config.timeoutMs and memory persistence.autoSaveIntervalMs. A current-spelling artifact is read byte for byte as before, stamps included. Draft PR #23089, Fixes #23029, Clause-②: no.",
    "registry_entries": [
    "job-timeout-to-timeout-ms — toMajor 18, retiredFromLoadPath true, retiredAfter 17.3.0, registry order 22 (jobs[].timeout -> timeoutMs)",
    "turso-config-timeout-to-timeout-ms — toMajor 18, retiredFromLoadPath true, retiredAfter 17.3.0, order 28 (turso config.timeout -> config.timeoutMs)",
    "memory-persistence-auto-save-interval-to-ms — toMajor 18, retiredFromLoadPath true, retiredAfter 17.3.0, order 27 (memory config.persistence.autoSaveInterval -> autoSaveIntervalMs, file and auto arms)",
    "Location: packages/spec/src/conversions/registry.ts (ALL_CONVERSIONS), which packages/metadata-core applyArtifactForwardConversions replays. NOT in packages/metadata-core itself, as the dispatch worded it. Neither file was touched."
    ],
    "reach": [
    "Measured on origin/main 7f2f081, BEFORE the change, with a scratch test (not committed). Route: a plugins[] bundle entry through materializeStackPlugin into new AppPlugin(bundle), the rule os serve (serve.ts) and os migrate (schema-migration-plugins.ts) mount by. It ran init, start and kernel:ready, with capturing services.",
    "No engines.protocol. The D1 handshake admits it with one warn ("declares no engines.protocol range; loading under protocol 18.0.0 without a compatibility check"). Conversion verdict: converted-undeclared, 3 notices.",
    "Result for that bundle: schedule calls [["sweep",null]], i.e. no options and no timeoutMs. Registered datasources: edge_db config {"url":"libsql://app.turso.io","timeout":30000} and local_cache persistence {"type":"file",…,"autoSaveInterval":5000}. connectDeclared got the same raw configs.",
    "engines.protocol ">=17.0.0": the same three drops, and no protocol warn at all.",
    "engines.protocol "^17.0.0": refused at D1 (ProtocolIncompatibleError, runtime protocol 18.0.0) before any reader runs. Loud, so it is not this defect.",
    "A packages[] bundle whose body carries jobs[].timeout is refused loudly by the entry parse (resolveArtifactPackageOrder: "packages[0] is not a package entry … manifest.jobs.0.timeout: job.timeout was removed"). Nothing is scheduled; the same before and after.",
    "Not measured at an HTTP door: the reach is measured at the plugins[] route layer only."
    ],
    "readers": [
    "Job scheduling (collectBundleJobs guard + scheduleAppArtifactJobs on kernel:ready). Before: this.bundle (raw), so job.timeoutMs was undefined and the options were undefined. After: this.forwardConverted(ctx).definition, so options are { retryPolicy: undefined, timeoutMs: 40 } and the 40 ms bound holds. collectBundleJobs' "stack jobs, else manifest.jobs" rule is unmoved on that copy; legacy manifest.jobs is now converted too. A handler job's ctx.bundle is now the converted copy.",
    "Code-defined datasource registration and codeDefinedDatasourceOwners. Before: this.collections.datasources, and packages[] bodies walked raw. After: convertedCollection(ctx, "datasources"), and bodies walked on the converted copy via resolveArtifactPackageOrder(forwardConverted(ctx).definition). Owner coordinates still come from the raw manifest. Stamps (origin/_packageId/_packageVersion/_provenance) are byte-identical, pinned by the CONTROL and by app-plugin.datasource-provenance (5/5 green).",
    "Reserved-name ("default") check. Before: this.collections.datasources. After: convertedCollection(ctx, "datasources"). Names are not rewritten by any conversion.",
    "connectDeclared input. Before: datasources and objects from this.collections. After: both from convertedCollection, and they are the SAME arrays the manifest service registered (identity pin). For a current artifact they are the authored arrays by reference.",
    "packages[] shape. A raw packages[] bundle with a retired key in a body never reaches any of these readers: it is refused at the entry parse, before and after. One arriving through loadArtifactBundle has converted bodies (#23027). For a packages[] bundle start() now runs forwardConverted, which the security block already did under the app-plugin registrar. Jobs: collectBundleJobs over the converted copy re-merges the converted top level and bodies. Datasources: the owner walk is over the converted bodies."
    ],
    "load_translations": "Measured, NOT changed. loadTranslations reads raw this.collections.translations. A zh-CN bundle carried the three major-18 translation retirements: settings (translation-per-app-settings-removed, retiredAfter 17.4.0), pages.home.components.f1.submitLabel (translation-component-submit-label-removed, 17.2.0) and dashboards.pulse.widgets.w1.subCaption (translation-widget-sub-caption-removed, 17.6.0). Booted through AppPlugin.start, getTranslations("zh-CN") on the in-memory fallback (createMemoryI18n) AND on FileI18nAdapter served all three retired keys. That is what GET /api/v1/i18n/translations/:locale serves. The converted copy strips all three (3 notices). So the raw read DOES change what the i18n service serves. Per the entries' own summaries, only settings has an effect: a per-app settings group still fills gaps in the merged tree. The other two are served and read by nothing. Reported as an out-of-scope finding below.",
    "tests": "All at 9c1768d (branch merged with origin/main 0aef788), through scripts/pm/os-verify-lock.sh. (1) pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 (the test script plus the worker cap): "Test Files 361 passed (361) / Tests 5162 passed | 19 skipped (5181)". --project repo: "Test Files 3 passed (3) / Tests 751 passed (751)". (2) pnpm --filter @objectstack/runtime run typecheck: VERDICT command-exit 0, "check:test-typecheck: OK … 27 file(s) / 190 error(s) / 68 pinned signature(s) held". The first attempt was queue-timeout 99 (NOT MEASURED) and was re-run in the same slot. tsc --listFiles -p tsconfig.test.json compiles app-plugin-converted-readers.test.ts with 0 errors in it. (3) Named pins, --reporter=verbose: 11 files, 81 tests, all green. They are app-plugin-converted-readers 6, app-plugin-converted-collections 8, standalone-stack-seeder-declaration-copy 13, app-plugin-config-door-api-items 6, app-plugin-api-matcher-absent-warning 3, app-plugin.datasource-provenance 5, app-plugin.jobs 8, app-plugin.option-b-packages 6, app-plugin.job-data-reach 8, app-plugin-artifact-forward-conversion 11, app-plugin.manifest-security-collision 7. (4) New file packages/runtime/src/app-plugin-converted-readers.test.ts, 6 tests, all through the plugins[] route (materializeStackPlugin): the jobs[].timeout pin (options { retryPolicy: undefined, timeoutMs: 40 } plus the run rejecting "exceeded CPU budget of 40ms"); the turso and memory pins (registered toStrictEqual converted plus stamps, connectDeclared converted); the one-conversion identity pin; CONTROL current spellings (byte-identical stamps, authored arrays by reference, authored object unstamped); CONTROL manifest globs (nothing registered or connected).",
    "ablation": "Ran inside os-verify-lock, through scripts/ablation-replace.mjs WRAP mode plus a bash trap (restore = git checkout HEAD -- ABS_PATH, blob compared with HEAD, git diff HEAD empty), on committed 372ea7f. app-plugin.ts reaches the suite by relative source import, so no dist or rebuild is involved. Every leg: "ok mutation landed: anchor 1 -> 0, blob 653250ea2154 -> NEW". Every restore: "ok restored: blob == HEAD (653250ea2154) and git diff HEAD is empty", and the trap's final "RESTORE PROVEN". Leg A (scheduleAppArtifactJobs back to this.bundle): exactly the jobs[].timeout pin red, "expected undefined to deeply equal { retryPolicy: undefined, …(1) }", 1 failed | 5 passed. Leg B (convertedCollection returns this.collections[key]): turso, memory and one-conversion red (diff shows "timeoutMs": 30000 against "timeout": 30000), 3 failed | 3 passed, both CONTROLs green. Leg C (raw-source guard deleted): exactly the manifest-globs CONTROL red, connectDeclared handed datasources ["src//*.datasource.ts"], 1 failed | 5 passed. Direction observed: turned red, as predicted for every leg.",
    "gates": {
    "head": "9c1768d541",
    "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with the claim's 3 paths and run with none: both 63, byte-identical lists. Run first on 167c450 and again on the merged 9c1768d: identical. stderr names objectstack-ai/objectstack at that commit.",
    "reconciliation": "dispatch-gates --repo objectstack-ai/objectstack --ran FILE (each line "CMD :: exit N"): "Run reconciliation — 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN." / "✓ dispatch-gates --ran: 63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED (a DERIVED zero — all 63 recorded an exit code and none of them is 3)."",
    "note": "pnpm check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (exit 3: 36 packages had no dist). It was re-run after turbo run build --filter=!@objectstack/docs (73/73 successful, 54 cached) and exited 0. That re-run is the line below.",
    "commands": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
    "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 :: ✓ check-adr-0087-registration --self-test: 548 assertions over real temp git repos (real scan()/assertInputs() path)",
    "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@17.7.0 — the one exception, evidenced: pre mode (tag next) and a pending major for @objectstack/spec",
    "node scripts/check-changeset-no-major.mjs --self-test :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
    "node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every $TURBO_ROOT$ input of a build Build Core runs) and all 11 sc",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0 :: check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 11110 tracked file(s),",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
    "node scripts/check-comment-mask-adoption.mjs :: exit 0 :: OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/
    + examples/, all 14 recorded and every recorded row still reached (13 unconverted, 1 s",
    "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 :: PASS check-comment-mask-adoption --self-test (0 failure(s))",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8896 files, 0 disagree, 0 unparseable, 166.6s (comparator self-test: 26 cases pass).",
    "node scripts/check-dts-emitted.mjs --self-test :: exit 0 :: check-dts-emitted self-test: all assertions passed.",
    "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
    "node scripts/check-empty-changeset.mjs --self-test :: exit 0 :: ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
    "node scripts/check-issue-citations.mjs :: exit 0 :: ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
    "node scripts/check-keyed-text-bounds.mjs :: exit 0 :: ✓ check:keyed-text-bounds: 112 .object.ts files under packages/
    + apps/
    * + examples/** (walk is repo-wide; 0 outside), 117 object declarations, 242 declared index ent",
    "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 :: PASS check-keyed-text-bounds --self-test (0 failure(s))",
    "node scripts/check-platform-object-tenancy-census.mjs :: exit 0 :: ✓ platform-object tenancy census matches the tree: 84 platform-namespace objects, 49 in the machinery's reach, 35 outside it, every exclusion explained by a declaration o",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 :: ✓ check-platform-object-tenancy-census self-test: all checks pass (84 objects, 35 outside the machinery)",
    "node scripts/check-plugin-teardown-shape.mjs :: exit 0 :: ✓ check:plugin-teardown-shape: 75 Plugin implementation(s) across 8310 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits ",
    "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 :: ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every roster name",
    "node scripts/check-registry-log-declared.mjs :: exit 0 :: OK: 73 vitest-running package(s) walked, 10 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).",
    "node scripts/check-registry-log-declared.mjs --self-test :: exit 0 :: self-test OK: 17 cases + level-vocabulary read + population declaration + real-tree selection floor.",
    "node scripts/check-rest-log-spy-declared.mjs :: exit 0 :: OK: 31 of 280 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
    "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 :: check-rest-log-spy-declared self-test reached its verdict: 23 case(s), 0 failure(s).",
    "node scripts/check-system-context-census.mjs :: exit 0 :: check-system-context-census: OK — 131 elevation read sites in 20 packages across 59 files, living in 111 symbol(s); the page cites 124 symbol(s) against 124 required, ove",
    "node scripts/check-system-context-census.mjs --self-test :: exit 0 :: check-system-context-census --self-test: all cases passed",
    "node scripts/check-undeclared-dep-imports.mjs :: exit 0 :: ✓ check:undeclared-dep-imports: 81 workspace packages under packages/
    + apps/** + examples/, 3046 non-test src files, 2543 @objectstack/* specifiers (0 assembled, not",
    "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 :: PASS check-undeclared-dep-imports --self-test (0 failure(s))",
    "node scripts/docs-audit/check-affected-docs.mjs :: exit 0 :: ✓ affected-docs self-test: 605 cases pass.",
    "node scripts/docs-audit/check-drift-comment.mjs :: exit 0 :: ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 :: ✓ self-test passed",
    "node scripts/release-pending-publish.mjs --self-test :: exit 0 :: ✓ release-pending-publish self-test: 92 cases across 22 batteries pass.",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 :: ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3051 source file(s) all carry their unit in the key name (or in a sibling unit, or under a declare",
    "pnpm check:changeset-gate-self-tests :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
    "pnpm check:cross-package-test-inputs :: exit 0 :: OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked root(s) judged, 3 on",
    "pnpm check:doc-authoring :: exit 0 :: ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 94810 string(s) read in 1319 parsed source(s) (seen floor 40000 st",
    "pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states "#6664 census: 2 ruled consumers". This gate polic",
    "pnpm check:dts-closure :: exit 0 :: check-dts-closure: 31 built package(s) swept - 116/116 declared declaration file(s) present across 31 package(s); 0 built package(s) declare no declaration entry point an",
    "pnpm check:dual-build-cjs-loads :: exit 0 :: ✓ check:dual-build-cjs-loads — 107 published require entry point(s) across 66 package(s) load; 717 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree",
    "pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: 909 (file, verb) row(s) held by the RETAINED ledger — a pin that leaves names itself.",
    "pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (11110 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare).",
    "pnpm check:issue-citations :: exit 0 :: ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, both board strategie",
    "pnpm check:lean-entry-closure :: exit 0 :: ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
    "pnpm check:logger-receiver-detach :: exit 0 :: OK every log channel keeps its receiver: 3344 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
    "pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 11101 text file(s) -- 11101 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
    "pnpm check:objectql-double-limit :: exit 0 :: OK ObjectQL double limit conformance holds: 483 double(s) graded, 289 apply the caller's bound or refuse it loudly.",
    "pnpm check:objectui-changeset :: exit 0 :: ✓ objectui-range --self-test: all checks passed",
    "pnpm check:org-identifier :: exit 0 :: check-org-identifier: OK (3366 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
    "pnpm check:page-declaration-shape :: exit 0 :: check-page-declaration-shape: OK — 36 page entries across 3367 sources under packages/
    , examples/, apps/ all reach the kernel through a discoverable declaration (:", "pnpm check:pm-changeset-deadline-census :: exit 0 :: ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero a reading, the verdict truth ta", "pnpm check:published-files :: exit 0 :: ✓ check:published-files — 70 publishable package(s) of 81 workspace member(s) declare a fileswhitelist that covers every entry point plus CHANGELOG.md and admits no te", "pnpm check:query-options-erasure :: exit 0 :: ✓ query-options-erasure ratchet holds: 65 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file under packages/ is c", "pnpm check:refd-timer-probe :: exit 0 :: OK check-refd-timer-probe: 8890 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.", "pnpm check:slot-lookup :: exit 0 :: ✓ slot-lookup ratchet holds: 104 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is covered bypnpm li",
    "pnpm check:sourcemap-no-sources-content :: exit 0 :: check-sourcemap-no-sources-content: 31 built package(s) swept - 170 map(s), none embed source text.",
    "pnpm check:stack-collection-maps :: exit 0 :: ✓ check-stack-collection-maps: 8 enumerations reconciled against 33 declared collections (17 waiver rows, each with a reason).",
    "pnpm check:swallow-census-controls :: exit 0 :: ✓ measure-durability-swallow-family self-test, gated families (#13919): 3 negative control(s) yield none, 2 regression control(s) stay clear, 6 resolution control(s) reso",
    "pnpm check:test-source-alias :: exit 0 :: check-test-source-alias OK — 73 packages with tests scanned; 60 registered as still resolving a workspace dep through dist/; 53 published subpath(s) resolved through ev",
    "pnpm check:tier-file-adoption :: exit 0 :: OK: 81 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
    "pnpm check:type-check-coverage :: exit 0 :: check-type-check-coverage: OK — 79/81 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https://github.com/objectstack-ai/objec",
    "pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 174.4s, 26 raw tsc error(s) total, none above its recorded number.",
    "pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 73 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 49, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS 8 -",
    "pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 506 matcher(s) discovered, 506 answer the combinator battery correctly or refuse it loudly (327 refuse)."
    ],
    "lint": "Narrowed to the touched files, with three proofs. (1) Population: eslint --print-config puts packages/runtime/src/app-plugin.ts (6 rules) and app-plugin-converted-readers.test.ts (5 rules) in the population; the .changeset .md is outside it. (2) Count: eslint --no-inline-config --format json over the 3 files reports 3 results: the 2 .ts files with 0 errors and 0 warnings, and the .md with "File ignored because no matching configuration was supplied". Exit 0. (3) Invariance: every parserOptions in eslint.config.mjs is { ecmaVersion: latest, sourceType: module }, with no project or projectService, so type-aware linting is off. The applied rules are AST selectors (no-restricted-imports, no-restricted-syntax, slot-lookup, query-options, verify-stand-in, comment-swallow), and the config reads only its own baseline JSON files, which are untouched. So this diff cannot move an untouched file's verdict. pnpm lint (whole repo) is CI's."
    },
    "line_budget": "vs origin/main: 3 files, +281 / -11. packages/runtime/src/app-plugin.ts +49/-11; packages/runtime/src/app-plugin-converted-readers.test.ts +219/-0; .changeset/23029-app-plugin-converted-readers.md +13/-0. No skills/** or governed surface, and far below the 3,000-changed-line class.",
    "files_changed": [
    "packages/runtime/src/app-plugin.ts (the job and datasource readers, the new private convertedCollection, the codeDefinedDatasourceOwners doc lines)",
    "packages/runtime/src/app-plugin-converted-readers.test.ts (new)",
    ".changeset/23029-app-plugin-converted-readers.md (@objectstack/runtime patch, body carries Clause-②: no)"
    ],
    "labels": "Zero label writes. The dispatch names no label, and skip-changeset does not apply because the PR ships a runtime patch changeset. Read back: PR #23089 carries labels [].",
    "deviations": [
    "The dispatch placed the conversion registry in packages/metadata-core. It is packages/spec/src/conversions/registry.ts, read by metadata-core. Read only.",
    "connectDeclared's objects input was switched together with its datasources. connectDeclared is a named reader, and objects is part of that call.",
    "codeDefinedDatasourceOwners' docblock lines naming its list were updated. forwardConverted's own docblock was NOT touched, so it still lists only its first two registrars.",
    "origin/main 0aef788 (2 commits, spec page.zod and plugin-hono-server, disjoint) was merged before the final verification. Merge commit 9c1768d, pushed. AGENTS.md §10 pre-PR refresh.",
    "The whole-package test was run as exec vitest run --project local --maxWorkers=2 (the package test script is vitest run --project local), plus --project repo.",
    "Commit trailers are the model-free pair AGENTS.md prescribes (Co-authored-by: Claude), not the harness reminder's model-named trailer. The merge commit carries git's default message, with no trailers.",
    "The reach and translation measurements came from an uncommitted scratch test, deleted from the worktree after use. The new committed test is the only test file."
    ],
    "mcp_calls": "0 — no MCP tool was called",
    "api_writes": "2. (1) ONE fleet-write relay dispatch (with-fleet.sh --via dispatch; request fw-20261012T192935Z-bd0cb9, run 38298006333, conclusion success) carrying ONE pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft, base main), then POST /repos//issues/23089/assignees [os-steve]. The relay read the body back: 10632 bytes sent, 10632 stored, identical. (2) This os-dev-report comment: POST /repos//issues/23029/comments via scripts/pm/post-stamped.mjs. git push is not REST: 4 pushes of the branch (the empty-branch probe, the fix, the changeset, the main merge).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: named producer, the card's own: a plugins[] bundle entry or new AppPlugin(bundle) built by 17.0–17.3 tooling, declaring no engines.protocol or a range that admits 18 · evidence: bindAppArtifactHandlers(ql, this.bundle, …) in AppPlugin.start binds hooks from the RAW bundle. Measured on that route: hooks [{ name: audit, timeout: 5000 }] (retired hook-timeout-to-timeout-ms, retiredAfter 17.3.0) reach ql.bindHooks as {"name":"audit","timeout":5000}, with no timeoutMs. packages/objectql/src/hook-wrappers.ts:372 reads only meta.timeoutMs, so the hook runs unbounded with no notice. Seam: spec:hook.timeoutMs (hook-timeout-to-timeout-ms) → runtime:packages/runtime/src/app-plugin.ts AppPlugin.start bindAppArtifactHandlers → objectql hook-wrappers · family: AppPlugin raw readers (#22994, #23029) — for the family's closing card, NOT a single-point card · dedupe words: bindAppArtifactHandlers raw bundle hook timeout timeoutMs; AppPlugin hooks retired key converted copy; hook-timeout-to-timeout-ms artifact plugins entry",
    "class: a · reach: named producer, as above · evidence: the card's own deferred loadTranslations measurement, now measured. Raw this.collections.translations makes getTranslations("zh-CN") serve settings, pages…components…submitLabel and dashboards…widgets…subCaption, on createMemoryI18n and FileI18nAdapter alike. The converted copy strips all three. The per-app settings group still fills gaps in the merged tree, which the translation-per-app-settings-removed entry says no longer happens. Seam: spec:translation-per-app-settings-removed / translation-component-submit-label-removed / translation-widget-sub-caption-removed → runtime:AppPlugin.loadTranslations(this.collections.translations) → i18n getTranslations · family: AppPlugin raw readers — for the family's closing card · dedupe words: loadTranslations raw translations retired settings; AppPlugin translations converted copy; per-app settings translation served",
    "carrier: 承接者:无 · noted in PR #23089 Acceptance notes, not filed. Record-spelled datasources ({ edge_db: {…} }) are not converted: mapDatasources walks arrays only. Measured: applyArtifactForwardConversions over a record-spelled turso config.timeout gives 0 notices and leaves it unconverted. AppPlugin's readers accept that spelling, but no producer is named (defineStack normalizes maps to arrays, and os build emits arrays).",
    "carrier: 承接者:无 · observation, not filed. On the plugins[] route, a packages[] bundle from 17.x tooling with a retired key in a BODY is refused loudly at the entry parse (INVALID_ARTIFACT_PACKAGE_ENTRY). The same artifact through loadArtifactBundle converts at load and boots. The refusal is loud and names the removed key, so it is not a silent drop."
    ]
    }

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #23089 at 9c1768d541 (Fixes #23029). It lands when every check on the head is green

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-12T19:35Z · holder of claim 6129944075. Report: os-dev-report 6131223758. Thread-read: 6131223758.

    Checklist, read against GitHub:

    • PR shape: draft, base main, line 1 Fixes #23029, line 2 Clause-②: no, assignee os-steve.

    • Scope: 3 files, +281 / −11:

      • app-plugin.ts (+49 / −11);
      • a new test (+219);
      • the changeset.

      origin/main 0aef788816 was merged in with no conflict. check-governed-merges --pr 23089: not governed.

    • Premise and reach, measured on 7f2f081e8a (the seat's repricing after PR fix(metadata-core): the artifact forward conversion walks every packages[].manifest body of an option-B artifact (ADR-0130 D4) #23045):

      • The registry carries all three entries: job-timeout-to-timeout-ms, turso-config-timeout-to-timeout-ms and memory-persistence-auto-save-interval-to-ms. Each has retiredAfter 17.3.0. They live in packages/spec/src/conversions/registry.ts, which metadata-core replays; the dispatch's "metadata-core" was wrong, and no file there was touched.
      • A plugins[] bundle entry through materializeStackPlugin with no engines.protocol, or with >=17.0.0, scheduled the job with no timeout and registered and connected both datasources with the retired keys.
      • ^17.0.0 is refused loudly at the D1 handshake.
    • The decision, read in the diff: the readers take the boot's one conversion, with no second pass.

    • Pins, all through the plugins[] route:

      • jobs[].timeout schedules as timeoutMs: 40, and the run is bounded at 40 ms;
      • the turso and memory datasources register and connect with the converted keys;
      • a one-conversion identity pin;
      • CONTROL: the current spellings are byte-identical, stamps included, with the authored arrays passed by reference;
      • CONTROL: the manifest's globs are neither registered nor connected.

      Three ablation legs (A jobs, B the collection read, C the raw-source guard) each red exactly their pins. They ran inside the lock with a trap, and each restore was proven.

    • Behaviour change, stated in the changeset: a handler job's ctx.bundle is now the converted copy. That is the copy every other registrar reads, as ADR-0087's "one conversion per boot" requires.

    • The loadTranslations reading (assumption 5): measured, not changed. On that route the raw read serves three retired translation keys, and the converted copy strips them. Only the per-app settings group has an effect: it still fills gaps in the merged tree. Filed by the seat with the hook reader below, as one family card.

    • Out-of-scope notes:

      • Two raw readers remain in this family. bindAppArtifactHandlers(ql, this.bundle, …) hands a hook's retired timeout to ql.bindHooks with no timeoutMs, so the hook runs unbounded. loadTranslations serves the retired keys. Both are measured on the same route. The seat files ONE family card for them, through the filing gate.
      • A record-spelled datasources map is not walked by the conversion. No producer emits it. Recorded in the PR's acceptance notes.
      • A 17.x packages[] bundle with a retired key in a body is refused loudly at the entry parse on the plugins[] route. That is not a silent drop.
    • Changeset .changeset/23029-app-plugin-converted-readers.md: @objectstack/runtime patch, Clause-②: no. No contract-review face.

    • Tests and gates at 9c1768d541:

      • @objectstack/runtime: 361 files, 5162 tests (local), plus 751 tests (repo).
      • Typecheck exits 0.
      • 11 named pin files: 81 tests green.
      • 63 gates derived with no paths: 63 run, all exit 0, 0 NOT MEASURED.
      • Lint is narrowed with the three-part proof.
    • CI on 9c1768d541 at this stamp: latest run per name, 24 success and 6 skipped, with 8 in progress. Not green yet.

    • Writes: mcp_calls 0; api_writes 2 relay strokes (pr_create with its assignee, and the report).


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23089 → 6662e3f590 (Fixes #23029). The card closes completed

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-12T21:41Z · holder of claim 6129944075, released by this landing.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:clipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions