You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
HttpTestAdapter's record action types resolve the DEFAULT API mount only — a deployment that sets api.apiPath or crud.dataPrefix is unreachable #7983
HttpTestAdapter (packages/core/src/qa/http-adapter.ts) no longer hard-codes /api/data. Its record action types (create_record, read_record, update_record, delete_record, query_records) build their URLs from the two schemas RestServer itself resolves from:
RestApiConfigSchema → apiPath ?? ${basePath}/${version} (the expression RestServer.getApiBasePath() evaluates)
CrudEndpointsConfigSchema.dataPrefix
which is what closed the 404s. But it reads their defaults, because the adapter is handed an origin and nothing else — it never sees the deployment's config.
The gap
Both pieces are configurable, and a deployment that moves the mount is invisible to the adapter:
config
server serves
adapter asks
result
stock
/api/v1/data/:object
/api/v1/data/:object
✅
api.apiPath: '/api/2026-01'
/api/2026-01/data/:object
/api/v1/data/:object
❌ 404
crud.dataPrefix: '/objects'
/api/v1/objects/:object
/api/v1/data/:object
❌ 404
The failure reads exactly like the one #7848 fixed — a 404 that looks like the suite author's own URL mistake rather than a platform limitation. Today's workaround is to write those steps as api_call, which takes the path it is given; that is what the shipped examples/app-showcase/qa/platform-smoke.test.json does throughout.
The shape a fix probably takes
@objectstack/client already answers this question and is the precedent worth copying rather than re-inventing (packages/client/src/index.ts, getRoute): prefer the server's own routes.data from GET {base}/discovery, fall back to the convention when discovery is unavailable or the key is absent. That makes the adapter correct against a re-prefixed host without adding a second declaration of the mount.
Two things to decide rather than assume:
Bootstrap. Discovery itself is found at a conventional path, so the fallback problem does not disappear, it moves one level up — the client has the same property. Is that acceptable here, or should os test grow a flag (--api-path) so an operator can state it?
Cost and failure mode. One memoised probe per run is cheap, but "discovery is disabled on this host" must degrade loudly (Route & surface ownership §3), not silently produce the same 404 with a longer path to the diagnosis.
Not urgent, and why
Every fixture boot in the repo — showcase, CRM, the platform-checklist runs — is stock, so nothing in-repo hits this. It is filed so the limit is a tracked decision rather than a surprise for the first deployment that re-prefixes its API. Recorded in docs/qa/platform-checklist/areas/cli.json (cli.qa-suite-execution, fixtures.knownGaps) and in content/docs/deployment/cli.mdx, so it is stated where an author would look.
Triage: finding (held), domain:cli confirmed (packages/core/src/qa/http-adapter.ts is qa/cli-lane surface), type Bug.
Grade rationale: the filer's own framing is accepted — zero in-repo fixtures hit this (every boot is stock), the limit is documented where an author would look (fixtures.knownGaps + deployment/cli.mdx), and the fix carries two genuine open choices (discovery-probe bootstrap vs an --api-path flag; loud-degradation contract). Those choices deserve a promotion-time decision, not a hold-time guess.
Restart conditions: first report of a deployment that re-prefixes its API mount hitting this, or any queued work touching HttpTestAdapter / os test config plumbing (fold this in, following the @objectstack/clientgetRoute discovery precedent the card names).
State-machine migration, maintainer-authorized (2026-08-13, live triage session: 「我授权你先迁标签」; semantics on #8449): this card is a graded hold — verdict and restart conditions are in the grading comment(s) above — so finding → pm:on-hold. Verdict, restart conditions, domain unchanged.
Requeued pm:on-hold → pm:queue under the maintainer's Bug-branch ruling for the on-hold stock reclassification (#9043 scope addendum): the close-default 「有些暂时不准备开发的应该直接关闭。」 does not apply to type:Bug holds — this is a reproducible, user-reachable defect (any deployment setting api.apiPath or crud.dataPrefix gets 404s from HttpTestAdapter), and a held bug is a hidden bug because the selection order's Bug priority only works on queued cards. Relayed by the skills-seat sweep, session session_017TNzEetykdh7ceZGwuAPLq.
Claim: PM dispatch seat, round 8
Session: session_012WKSnqAaoqtW3QX7SSf1Vk
Branch: claude/issue-7983-http-adapter-mount-resolution
Worktree: objectstack-issue-7983
Domain: domain:cli
File surface: packages/core/src/qa/http-adapter.ts (+ its tests), docs/qa/platform-checklist/areas/cli.json (the knownGaps entry this card wrote), content/docs/deployment/cli.mdx (same), .changeset/** (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus — node scripts/pm/dispatch-gates.mjs this round reports no path-derived mandate (floor sonnet · default opus · ceiling fable). Clause ② judged from card CONTENT and does not fire, because of the Zone 1 ruling below: the discovery-probe route adds no public surface and widens no accepted set. The alternative that would have engaged the manual floor — a new os test --api-path flag — is refused here for exactly that reason.
Serial constraints cleared:
packages/core/src/qa/http-adapter.ts: free. One touch since the card was filed (445ae4deb, an unrelated auth-locale change); no open PR in this repo writes packages/core.
The promotion-time decision this card was waiting on
Triage graded this a hold in part because "the fix carries two genuine open choices … those choices deserve a promotion-time decision, not a hold-time guess." The 2026-08-16 requeue moved it pm:on-hold → pm:queue under the Bug-branch ruling without making that decision, so it is queued with its shape still open. Making it now, before dispatch, rather than letting a dev guess in a worktree:
Ruled: the discovery probe. ⛔ Not an --api-path flag.
Choice 1 is not evenly balanced once the manual floor is applied. A new os test --api-path flag widens the public CLI surface — that is a Feature, it sits on the maintainer-only floor, and it is not mine to authorize or a dev's to build. The discovery probe adds no public surface and re-uses a shipped, precedented pattern (@objectstack/client's getRoute + memoised discoveryInfo, packages/client/src/index.ts:492,524-563). So the route is decided by which option this seat is allowed to decide, not by preference.
⚠️ If the probe turns out not to work without an operator escape hatch, that is a stop-and-report, not a licence to add the flag. It escalates to the maintainer.
Ruled: loud degradation is a requirement, not a choice.
Choice 2 was never really open — Route & surface ownership §3 already governs it, and the card states the failure mode precisely: "discovery is disabled on this host" must degrade loudly, naming the mount it resolved and why, not reproduce the same 404 with a longer path to the diagnosis. One memoised probe per run.
The part I expect to be wrong, handed over as an assumption
⚠️The precedent does not solve the bootstrap problem, and it may not be solvable for one of the two configs.@objectstack/client hardcodes ${baseUrl}/api/v1/discovery and falls back to .well-known — so a host that moves api.apiPath moves discovery itself out from under the probe. My reading is therefore that crud.dataPrefix (which moves the data path under a findable api base) is recoverable, while api.apiPath (which moves the base the probe needs to find) may not be. A fix that honestly closes only one of the two rows in the card's table is an acceptable outcome — but then the knownGaps line is narrowed, not deleted, and the PR body states which row survives. This is the dev's to measure, not to inherit from me.
Full brief, including the read-coupling with #9292's /discovery handler (pin against whatever routes.data says, never against a literal /api/v1/data, so the pins survive that card), went to the agent.
Dev process died in a container restart — re-dispatched as a recovery run. The claim is unchanged and this card was never unattended. PM domain:cli seat (#6024), session session_012WKSnqAaoqtW3QX7SSf1Vk.
Recording this so the gap in the timeline is a known cause rather than a mystery, and so nobody reads the empty remote branch as an abandoned claim.
Cause is known and is not the dev's: the container hosting this session restarted at ~21:5xZ and took the running os-dev process with it. That is not a maintainer abort (no signal, no instruction) and not the self-death shape that triggers stale-claim reclaim — and the reclaim test does not fire here anyway, since the claim is this same seat's and only hours old.
What survived, verified before re-dispatching (read-only inspection — this seat writes no code):
The worktree is intact with one commit, 951627219, carrying the whole fix: packages/core/src/qa/http-adapter.ts and its test file.
Uncommitted but present: the changeset, and the two documentation narrowings this card owed (docs/qa/platform-checklist/areas/cli.json, content/docs/deployment/cli.mdx).
The remote branch is empty — it still points at origin/main, so nothing had been pushed and no PR exists. That is the only reason this needed a dispatch rather than a nudge.
The measured result, which I am recording now because it answers the question this card was held on for months — and which the recovery agent has been told to re-verify rather than inherit:
deployment
before
after
stock
created
created
crud.dataPrefix: '/objects'
HTTP Error 404
created
api.apiPath: '/api/2026-01'
HTTP Error 404
HTTP Error 404, now naming the mount
That is exactly the split the dispatch predicted as its expected falsification: dataPrefix is recoverable, apiPath is not, because apiPath moves the base that /discovery itself sits under. So the knownGaps entry gets narrowed, not deleted — which is what the brief said the honest outcome would look like.
The dev also established a control worth keeping on the record: /.well-known/objectstack advertises the dispatcher's${prefix}/data, measured as /api/v1/data under all three configs above, so probing it would attach a false provenance ("discovery told us") to the same 404. It is deliberately not probed.
The recovery dispatch's one added instruction: state explicitly which of the surviving work was re-verified and which was taken as-is. A test run that passed before the restart is not evidence now.
{
"issue": 7983,
"status": "done",
"branch": "claude/issue-7983-http-adapter-mount-resolution",
"pr": "https://github.com/objectstack-ai/objectstack/pull/9385",
"premise_still_valid": true,
"summary": "RECOVERY run. The predecessor's commit 951627219 (http-adapter.ts + http-adapter.test.ts) and its three uncommitted files survived intact; I re-verified rather than trusted them, committed the remainder as 99592d0ab, pushed, and opened draft PR #9385. HttpTestAdapter now resolves the Data Protocol mount from one memoised GET {apiBase}/discovery per run, addressing whatever routes.data advertises, falling back to the RestApiConfigSchema + CrudEndpointsConfigSchema convention with a loud diagnostic that names the mount, the failed probe and the remedy; every 404/405 from a record action carries the same provenance, while 400/422 stays undecorated. Zone 1 honoured: no --api-path flag, packages/client untouched, no content/docs change beyond deployment/cli.mdx. RE-VERIFIED BY ME: the full before/after table (re-derived independently, see tests), the knownGaps and cli.mdx narrowing (both narrow the apiPath row rather than delete it), the whole test suite, the gate union, the type-check ratchet, absence of control bytes, and that the built dist carries the new code. TAKEN AS-IS: only the predecessor's claim that its original table came from an actually booted three-config stack — I could not re-observe that boot, so I re-derived every row by a different route instead, and all three reproduce.",
"tests": "All on final commit 99592d0ab (git rev-parse --short HEAD), clean tree, gate union run AFTER that commit. (1) pnpm --filter @objectstack/core test => 'Test Files 35 passed (35) / Tests 857 passed (857)', exit 0. (2) TYPECHECK NOTE, not a template value: @objectstack/core has NO typecheck script, so pnpm --filter @objectstack/core typecheck would match zero scripts and exit 0 without checking anything — I did not report that as a pass. The real measurement is the ratchet: pnpm check:type-check-debt (workspace closure built first via turbo run build --filter=./packages/* --filter=./packages/*/*, 70/70 cached FULL TURBO) => '33 ledger entr(ies) re-measured in 214.2s, 1926 raw tsc error(s) total, none above its recorded number. surplus: none — every entry sits exactly at its measurement, so any new error is red.' MEASURED, green. (3) Gate union re-derived on my ACTUAL changed paths (node scripts/pm/dispatch-gates.mjs) and all run green: check:changeset-gate-self-tests, check:cross-package-test-inputs, check:docs-audit-scope, check:docs-redirects, check:role-word, check:kernel-hook-pairs, check:test-source-alias, check:objectui-changeset, the four @objectstack/spec liveness checks (empty-state, liveness, strictness-ledger, variant-docs — each echoed its script name, so none was a zero-match silent green), scripts/check-adr-0087-registration.mjs, check-changeset-no-major.mjs, check-cross-package-test-inputs.mjs, check-empty-changeset.mjs, docs-audit/check-affected-docs.mjs. The derivation ALSO named a convention-triggered family the earlier list did not (new test file): check:query-options-erasure (ratchet holds, none new), check:engine-double-contract (OK, 317 pinned), check:where-matcher (253 matchers, 0 silently-wrong, none new), check:type-check-coverage. All green; nothing weakened, no threshold lowered, no baseline rebaselined, no test skipped. (4) TABLE RE-VERIFICATION, by a route independent of the predecessor's boot: constructed RestServer under the three configs and read BOTH bases back off the server's own normalized config (constructor is (server, protocol, config) — config is the THIRD argument; my first harness passed it first and produced a false 'row 3 does not reproduce', which was my own bug, corrected before drawing any conclusion). Result reproduces every row: stock serves /api/v1/data (before HIT, after HIT); crud.dataPrefix '/objects' serves /api/v1/objects (before /api/v1/data MISS 404, after /api/v1/objects HIT — row CLOSED); api.apiPath '/api/2026-01' serves /api/2026-01/data with discovery itself moved to /api/2026-01/discovery so the probe 404s (before MISS, after MISS, announced — row NOT closed). Mechanisms confirmed at source: rest-server.ts mounts discovery at ${basePath}/discovery, and its handler sets discovery.routes.data = ${realBase}${crud.dataPrefix}. The control is now STRONGER than three samples: /.well-known/objectstack is the runtime dispatcher's (dispatcher-plugin.ts serves it from getDiscoveryInfo(prefix)) and http-dispatcher.ts composes data: ${prefix}/data from the dispatcher's own prefix, never reading crud.dataPrefix or the REST apiPath — so it is structurally independent of both configs, which is why probing it would attach a false provenance to the same 404. Deliberately not probed, and pinned as such. (5) Corroboration: packages/rest direct-mount-base-follows-apipath.test.ts 6/6 passed, including 'advertises the moved bases in {apiPath}/discovery'. (6) Hygiene: grep -naP over all five changed files found no control bytes; built dist confirmed to carry the new probe code before any judgement was drawn; PR body audited so the only closing keyword pairs with #7983 and no bare '<'+letter can truncate it.",
"open_questions": [],
"out_of_scope_findings": []
}
PR #9385 (recovery run, final commit 99592d0ab). Enqueued; all six required checks are running clean, nothing red.
The PM's promotion-time ruling held, and its predicted falsification landed exactly
The dispatch ruled discovery probe, ⛔ no os test --api-path flag — decided by which option this seat is allowed to decide, since a new CLI flag widens the public surface onto the maintainer-only floor. Verified on the branch: zero occurrences of --api-path anywhere under packages/, with a control query (apiPath in the adapter, 7 hits) proving the search works. packages/client untouched, as required while #8684 writes it.
The handover also predicted the fix would close only one of the two rows, and it does — crud.dataPrefix closed, api.apiPath not, because apiPath moves the discovery document itself out from under the probe. So the knownGaps entry is narrowed, not deleted, which is exactly the honest outcome the brief described. I checked that specifically rather than accepting the claim: the row now states which config is reached and which is not, plus a revision: 3 history entry carrying the measured before/after.
Three things this run did that are worth more than the fix
It found its own harness bug before drawing a conclusion. Its first verification harness passed config as the first constructor argument when RestServer takes it third, which produced a false "row 3 does not reproduce". It caught and corrected that rather than reporting a falsification — the difference between a real finding and a self-inflicted one.
It refused a free green.@objectstack/core has no typecheck script, so pnpm --filter @objectstack/core typecheck matches zero scripts and exits 0 without checking anything. It explicitly declined to report that as a pass and measured the real thing instead — check:type-check-debt, on a built closure: "33 ledger entries re-measured, 1926 raw tsc errors, none above its recorded number. surplus: none." A zero-match command that exits 0 is the purest form of the aggregate-reads-green failure, and it named it.
It upgraded the /.well-known control from empirical to structural. The earlier run had three samples showing /.well-known/objectstack reporting /api/v1/data regardless of config. This run established why: the dispatcher serves it from getDiscoveryInfo(prefix) and composes data: ${prefix}/data from its own prefix, never reading crud.dataPrefix or the REST apiPath. That makes it structurally independent of both configs — so probing it would attach a false provenance ("discovery told us") to the same 404. Deliberately not probed, and pinned as such.
Recovery discipline
The dispatch's one added instruction was to state which surviving work was re-verified and which was taken as-is. It answered precisely: everything re-verified except the predecessor's claim that its table came from a booted three-config stack — which it could not re-observe, so it re-derived every row by an independent route instead, and all three reproduce. That is the right way to handle inherited evidence.
Gate union re-derived on the actual changed paths and green throughout, including a convention-triggered family the original dispatch list did not name (new test file ⇒ check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:type-check-coverage). Nothing weakened, no baseline rebaselined, no test skipped.
Activity
hotlong commentedon Aug 12, 2026
Triage:
finding(held),domain:cliconfirmed (packages/core/src/qa/http-adapter.tsis qa/cli-lane surface), type Bug.fixtures.knownGaps+deployment/cli.mdx), and the fix carries two genuine open choices (discovery-probe bootstrap vs an--api-pathflag; loud-degradation contract). Those choices deserve a promotion-time decision, not a hold-time guess.HttpTestAdapter/os testconfig plumbing (fold this in, following the@objectstack/clientgetRoutediscovery precedent the card names).本评论来自分诊座位(scheduled session
session_0199Rq2oEnNNRmdhmWwqUwvQ),不构成认领。Generated by Claude Code
hotlong commentedon Aug 13, 2026
State-machine migration, maintainer-authorized (2026-08-13, live triage session: 「我授权你先迁标签」; semantics on #8449): this card is a graded hold — verdict and restart conditions are in the grading comment(s) above — so
finding→pm:on-hold. Verdict, restart conditions, domain unchanged.Generated by Claude Code
os-zhuang commentedon Aug 16, 2026
Requeued
pm:on-hold→pm:queueunder the maintainer's Bug-branch ruling for the on-hold stock reclassification (#9043 scope addendum): the close-default 「有些暂时不准备开发的应该直接关闭。」 does not apply totype:Bugholds — this is a reproducible, user-reachable defect (any deployment settingapi.apiPathorcrud.dataPrefixgets 404s fromHttpTestAdapter), and a held bug is a hidden bug because the selection order's Bug priority only works on queued cards. Relayed by the skills-seat sweep, sessionsession_017TNzEetykdh7ceZGwuAPLq.Generated by Claude Code
os-zhuang commentedon Aug 17, 2026
Claim: PM dispatch seat, round 8
Session:
session_012WKSnqAaoqtW3QX7SSf1VkBranch:
claude/issue-7983-http-adapter-mount-resolutionWorktree:
objectstack-issue-7983Domain:
domain:cliFile surface:
packages/core/src/qa/http-adapter.ts(+ its tests),docs/qa/platform-checklist/areas/cli.json(theknownGapsentry this card wrote),content/docs/deployment/cli.mdx(same),.changeset/**(stop on breach; explain in the report)Container & model: M,
mode:subagent,model: opus—node scripts/pm/dispatch-gates.mjsthis round reports no path-derived mandate (floor sonnet · default opus · ceiling fable). Clause ② judged from card CONTENT and does not fire, because of the Zone 1 ruling below: the discovery-probe route adds no public surface and widens no accepted set. The alternative that would have engaged the manual floor — a newos test --api-pathflag — is refused here for exactly that reason.Serial constraints cleared:
packages/core/src/qa/http-adapter.ts: free. One touch since the card was filed (445ae4deb, an unrelated auth-locale change); no open PR in this repo writespackages/core.docs/qa/platform-checklist/**: free for this file. PR docs(qa,pm): qa-run joins the sweep exclusion list; run records owe defect extraction at close-out #9366 is open on that tree but touches onlyREADME.md,RUNNER.md,runs/README.md— notareas/cli.json. Verified by reading its file list, not by package prefix.packages/client/src/**is READ-ONLY for this card. automation: the run-resume route still answers HTTP 200 wrapping an inner {success:false} — the route #3962's status-code unification left behind #8684 is writing that package this round. ThegetRouteprecedent this card names is to be copied from, never edited.packages/runtime+packages/services/service-automation+packages/client; Report the business identifiers already minted twice by the tenancy split — an operator-facing inventory, on installs the #8686 backfill cannot repair #8928 →packages/cli/src/commands/**; RestServer.probeMcpServeable re-derives the request's environment instead of using the shared entry point — /discovery'smcpadvertisement can follow the wrong kernel #9120 (PR fix(rest): route probeMcpServeable through the shared environment entry point so /discovery's mcp advertisement follows the request's kernel #9291) →packages/rest/src/**. All disjoint from this surface.The promotion-time decision this card was waiting on
Triage graded this a hold in part because "the fix carries two genuine open choices … those choices deserve a promotion-time decision, not a hold-time guess." The 2026-08-16 requeue moved it
pm:on-hold→pm:queueunder the Bug-branch ruling without making that decision, so it is queued with its shape still open. Making it now, before dispatch, rather than letting a dev guess in a worktree:Ruled: the discovery probe. ⛔ Not an
--api-pathflag.Choice 1 is not evenly balanced once the manual floor is applied. A new
os test --api-pathflag widens the public CLI surface — that is aFeature, it sits on the maintainer-only floor, and it is not mine to authorize or a dev's to build. The discovery probe adds no public surface and re-uses a shipped, precedented pattern (@objectstack/client'sgetRoute+ memoiseddiscoveryInfo,packages/client/src/index.ts:492,524-563). So the route is decided by which option this seat is allowed to decide, not by preference.Ruled: loud degradation is a requirement, not a choice.
Choice 2 was never really open — Route & surface ownership §3 already governs it, and the card states the failure mode precisely: "discovery is disabled on this host" must degrade loudly, naming the mount it resolved and why, not reproduce the same 404 with a longer path to the diagnosis. One memoised probe per run.
The part I expect to be wrong, handed over as an assumption
@objectstack/clienthardcodes${baseUrl}/api/v1/discoveryand falls back to.well-known— so a host that movesapi.apiPathmoves discovery itself out from under the probe. My reading is therefore thatcrud.dataPrefix(which moves the data path under a findable api base) is recoverable, whileapi.apiPath(which moves the base the probe needs to find) may not be. A fix that honestly closes only one of the two rows in the card's table is an acceptable outcome — but then theknownGapsline is narrowed, not deleted, and the PR body states which row survives. This is the dev's to measure, not to inherit from me.Full brief, including the read-coupling with #9292's
/discoveryhandler (pin against whateverroutes.datasays, never against a literal/api/v1/data, so the pins survive that card), went to the agent.Generated by Claude Code
os-zhuang commentedon Aug 17, 2026
Dev process died in a container restart — re-dispatched as a recovery run. The claim is unchanged and this card was never unattended. PM
domain:cliseat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1Vk.Recording this so the gap in the timeline is a known cause rather than a mystery, and so nobody reads the empty remote branch as an abandoned claim.
Cause is known and is not the dev's: the container hosting this session restarted at ~21:5xZ and took the running
os-devprocess with it. That is not a maintainer abort (no signal, no instruction) and not the self-death shape that triggers stale-claim reclaim — and the reclaim test does not fire here anyway, since the claim is this same seat's and only hours old.What survived, verified before re-dispatching (read-only inspection — this seat writes no code):
951627219, carrying the whole fix:packages/core/src/qa/http-adapter.tsand its test file.docs/qa/platform-checklist/areas/cli.json,content/docs/deployment/cli.mdx).origin/main, so nothing had been pushed and no PR exists. That is the only reason this needed a dispatch rather than a nudge.The measured result, which I am recording now because it answers the question this card was held on for months — and which the recovery agent has been told to re-verify rather than inherit:
crud.dataPrefix: '/objects'HTTP Error 404api.apiPath: '/api/2026-01'HTTP Error 404HTTP Error 404, now naming the mountThat is exactly the split the dispatch predicted as its expected falsification:
dataPrefixis recoverable,apiPathis not, becauseapiPathmoves the base that/discoveryitself sits under. So theknownGapsentry gets narrowed, not deleted — which is what the brief said the honest outcome would look like.The dev also established a control worth keeping on the record:
/.well-known/objectstackadvertises the dispatcher's${prefix}/data, measured as/api/v1/dataunder all three configs above, so probing it would attach a false provenance ("discovery told us") to the same 404. It is deliberately not probed.The recovery dispatch's one added instruction: state explicitly which of the surviving work was re-verified and which was taken as-is. A test run that passed before the restart is not evidence now.
Generated by Claude Code
fix(qa): HttpTestAdapter resolves the data mount from discovery, and …
docs(qa): narrow the HttpTestAdapter mount gap to the apiPath row (#7983
os-zhuang commentedon Aug 17, 2026
{ "issue": 7983, "status": "done", "branch": "claude/issue-7983-http-adapter-mount-resolution", "pr": "https://github.com/objectstack-ai/objectstack/pull/9385", "premise_still_valid": true, "summary": "RECOVERY run. The predecessor's commit 951627219 (http-adapter.ts + http-adapter.test.ts) and its three uncommitted files survived intact; I re-verified rather than trusted them, committed the remainder as 99592d0ab, pushed, and opened draft PR #9385. HttpTestAdapter now resolves the Data Protocol mount from one memoised GET {apiBase}/discovery per run, addressing whatever routes.data advertises, falling back to the RestApiConfigSchema + CrudEndpointsConfigSchema convention with a loud diagnostic that names the mount, the failed probe and the remedy; every 404/405 from a record action carries the same provenance, while 400/422 stays undecorated. Zone 1 honoured: no --api-path flag, packages/client untouched, no content/docs change beyond deployment/cli.mdx. RE-VERIFIED BY ME: the full before/after table (re-derived independently, see tests), the knownGaps and cli.mdx narrowing (both narrow the apiPath row rather than delete it), the whole test suite, the gate union, the type-check ratchet, absence of control bytes, and that the built dist carries the new code. TAKEN AS-IS: only the predecessor's claim that its original table came from an actually booted three-config stack — I could not re-observe that boot, so I re-derived every row by a different route instead, and all three reproduce.", "tests": "All on final commit 99592d0ab (git rev-parse --short HEAD), clean tree, gate union run AFTER that commit. (1) pnpm --filter @objectstack/core test => 'Test Files 35 passed (35) / Tests 857 passed (857)', exit 0. (2) TYPECHECK NOTE, not a template value: @objectstack/core has NO typecheck script, so pnpm --filter @objectstack/core typecheck would match zero scripts and exit 0 without checking anything — I did not report that as a pass. The real measurement is the ratchet: pnpm check:type-check-debt (workspace closure built first via turbo run build --filter=./packages/* --filter=./packages/*/*, 70/70 cached FULL TURBO) => '33 ledger entr(ies) re-measured in 214.2s, 1926 raw tsc error(s) total, none above its recorded number. surplus: none — every entry sits exactly at its measurement, so any new error is red.' MEASURED, green. (3) Gate union re-derived on my ACTUAL changed paths (node scripts/pm/dispatch-gates.mjs) and all run green: check:changeset-gate-self-tests, check:cross-package-test-inputs, check:docs-audit-scope, check:docs-redirects, check:role-word, check:kernel-hook-pairs, check:test-source-alias, check:objectui-changeset, the four @objectstack/spec liveness checks (empty-state, liveness, strictness-ledger, variant-docs — each echoed its script name, so none was a zero-match silent green), scripts/check-adr-0087-registration.mjs, check-changeset-no-major.mjs, check-cross-package-test-inputs.mjs, check-empty-changeset.mjs, docs-audit/check-affected-docs.mjs. The derivation ALSO named a convention-triggered family the earlier list did not (new test file): check:query-options-erasure (ratchet holds, none new), check:engine-double-contract (OK, 317 pinned), check:where-matcher (253 matchers, 0 silently-wrong, none new), check:type-check-coverage. All green; nothing weakened, no threshold lowered, no baseline rebaselined, no test skipped. (4) TABLE RE-VERIFICATION, by a route independent of the predecessor's boot: constructed RestServer under the three configs and read BOTH bases back off the server's own normalized config (constructor is (server, protocol, config) — config is the THIRD argument; my first harness passed it first and produced a false 'row 3 does not reproduce', which was my own bug, corrected before drawing any conclusion). Result reproduces every row: stock serves /api/v1/data (before HIT, after HIT); crud.dataPrefix '/objects' serves /api/v1/objects (before /api/v1/data MISS 404, after /api/v1/objects HIT — row CLOSED); api.apiPath '/api/2026-01' serves /api/2026-01/data with discovery itself moved to /api/2026-01/discovery so the probe 404s (before MISS, after MISS, announced — row NOT closed). Mechanisms confirmed at source: rest-server.ts mounts discovery at ${basePath}/discovery, and its handler sets discovery.routes.data = ${realBase}${crud.dataPrefix}. The control is now STRONGER than three samples: /.well-known/objectstack is the runtime dispatcher's (dispatcher-plugin.ts serves it from getDiscoveryInfo(prefix)) and http-dispatcher.ts composes data: ${prefix}/data from the dispatcher's own prefix, never reading crud.dataPrefix or the REST apiPath — so it is structurally independent of both configs, which is why probing it would attach a false provenance to the same 404. Deliberately not probed, and pinned as such. (5) Corroboration: packages/rest direct-mount-base-follows-apipath.test.ts 6/6 passed, including 'advertises the moved bases in {apiPath}/discovery'. (6) Hygiene: grep -naP over all five changed files found no control bytes; built dist confirmed to carry the new probe code before any judgement was drawn; PR body audited so the only closing keyword pairs with #7983 and no bare '<'+letter can truncate it.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
os-zhuang commentedon Aug 17, 2026
ACCEPT — PM
domain:cliseat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1VkPR #9385 (recovery run, final commit
99592d0ab). Enqueued; all six required checks are running clean, nothing red.The PM's promotion-time ruling held, and its predicted falsification landed exactly
The dispatch ruled discovery probe, ⛔ no
os test --api-pathflag — decided by which option this seat is allowed to decide, since a new CLI flag widens the public surface onto the maintainer-only floor. Verified on the branch: zero occurrences of--api-pathanywhere underpackages/, with a control query (apiPathin the adapter, 7 hits) proving the search works.packages/clientuntouched, as required while #8684 writes it.The handover also predicted the fix would close only one of the two rows, and it does —
crud.dataPrefixclosed,api.apiPathnot, becauseapiPathmoves the discovery document itself out from under the probe. So theknownGapsentry is narrowed, not deleted, which is exactly the honest outcome the brief described. I checked that specifically rather than accepting the claim: the row now states which config is reached and which is not, plus arevision: 3history entry carrying the measured before/after.Three things this run did that are worth more than the fix
configas the first constructor argument whenRestServertakes it third, which produced a false "row 3 does not reproduce". It caught and corrected that rather than reporting a falsification — the difference between a real finding and a self-inflicted one.@objectstack/corehas notypecheckscript, sopnpm --filter @objectstack/core typecheckmatches zero scripts and exits 0 without checking anything. It explicitly declined to report that as a pass and measured the real thing instead —check:type-check-debt, on a built closure: "33 ledger entries re-measured, 1926 raw tsc errors, none above its recorded number. surplus: none." A zero-match command that exits 0 is the purest form of the aggregate-reads-green failure, and it named it./.well-knowncontrol from empirical to structural. The earlier run had three samples showing/.well-known/objectstackreporting/api/v1/dataregardless of config. This run established why: the dispatcher serves it fromgetDiscoveryInfo(prefix)and composesdata: ${prefix}/datafrom its own prefix, never readingcrud.dataPrefixor the RESTapiPath. That makes it structurally independent of both configs — so probing it would attach a false provenance ("discovery told us") to the same 404. Deliberately not probed, and pinned as such.Recovery discipline
The dispatch's one added instruction was to state which surviving work was re-verified and which was taken as-is. It answered precisely: everything re-verified except the predecessor's claim that its table came from a booted three-config stack — which it could not re-observe, so it re-derived every row by an independent route instead, and all three reproduce. That is the right way to handle inherited evidence.
Gate union re-derived on the actual changed paths and green throughout, including a convention-triggered family the original dispatch list did not name (new test file ⇒
check:query-options-erasure,check:engine-double-contract,check:where-matcher,check:type-check-coverage). Nothing weakened, no baseline rebaselined, no test skipped.Generated by Claude Code
fix(qa): HttpTestAdapter resolves the Data Protocol mount from discov…