Repository navigation
spec(integration): ConnectorProviderContext gains a host-provided package anchor, so a provider resolves an app-relative path against the app's root (#22423's spec stage) #22434
Description
Activity
- added and removed
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 2 (#22434: one optional host-provided package-anchor member on
ConnectorProviderContext, the spec stage of #22423) · 2026-10-09T09:14Z
Session:session_01VZqqwTj2wsihZEbfT6yyYN
Account:os-tesla(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-22434-provider-package-path
Worktree:objectstack-issue-22434
Domain:domain:spec
Seat:domain:spec#1
File surface (atorigin/mainda159f74e6; stop on breach and explain in the report):packages/spec/src/integration/connector-provider.ts(ConnectorProviderContext, today's one package-anchored memberloadPackageFileat:117) and its tests; the declared cross-lane pathpackages/services/service-automation/src/plugin.ts(createPackageFileLoaderat:206, handed to factories at:1977) and its tests; one.changeset/22434-*.md. ⛔ Notconnector-mcp: using the member as the stdio transport's working directory is #22423's stage.
Container & model:M,mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate; clause-② suspect surfacepackages/spec/src/**). Built at the default tier; the contract review atCONTRACT_REVIEW_TIERis owed before enqueue (a new member on a published interface).
Clause-②: yes
Responsibility:n/a — not a defect card (a contract gap split from #22423 by triage)
Thread-read: none
Serial constraints cleared: no open PR touchesconnector-provider.tsorservice-automation/src/plugin.ts(all 15 open PRs' file lists, read 2026-10-09T09:10Z). #22423 (domain:services,pm:blocked,Blocked-by:this card) follows this card's landing.objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22434,
"status": "done",
"branch": "claude/issue-22434-provider-package-path",
"pr": "#22465",
"session": "session_01VZqqwTj2wsihZEbfT6yyYN",
"premise_still_valid": true,
"summary": "ConnectorProviderContext gains one optional, host-provided member, resolvePackagePath(relativePath), the card's preferred confined shape: it resolves against the declaring stack/package root, returns the root for '.', refuses an empty, absolute (posix or drive-letter) or root-escaping path, reads nothing and checks no existence; its type equals loadPackageFile's (async, string in, string out). service-automation's materializer hands it beside loadPackageFile, anchored at the same packageRoot with the same process.cwd() default, and both members now call ONE private helper (resolveInsidePackageRoot) extracted from createPackageFileLoader, whose behaviour and three checklist-pinned error strings are byte-identical; createPackagePathResolver is exported from plugin.ts for tests only, not from the package index. H1 confirmed (lexical rule, no realpath: symlink escapes not handled today, matched exactly by sharing the helper); H2 confirmed (one production host; the three connector test ctx() helpers omit the optional member and still compile); H3: the liveness _note stays true and was not edited (check:liveness green); H4: consistent fallback, pinned. connector-mcp untouched (#22423 remains open).",
"tests": "All on head 159042a. Build (verify lock): pnpm turbo run build --filter='@objectstack/service-automation...' --concurrency=2 → 30/30 successful, VERDICT command-exit 0; spec dist carries resolvePackagePath. service-automation: vitest run --maxWorkers=2 → 178 files, 2187 passed; connector-materialization.test.ts verbose → 50 passed incl. 8 new #22434 cases. spec: vitest run --project local --maxWorkers=2 → 627 files, 18758 passed, 1 todo; connector-provider.test.ts verbose → 15 passed. Typecheck: pnpm --filter @objectstack/service-automation typecheck && pnpm --filter @objectstack/spec typecheck → VERDICT command-exit 0 (spec check:test-typecheck compiles connector-provider.test.ts, proven by A1). One-off proofs via scripts/ablation-replace.mjs WRAP mode on committed head, each restored and proven (blob == HEAD, git diff HEAD empty): RV1 (cross-package reverse verification, materializer hands a string) → service-automation tsc red: plugin.ts(2022,17) TS2322 'string or undefined' not assignable to the member's function type, i.e. tsc reads the rebuilt spec .d.ts; A1 (spec member made required) → check:test-typecheck red, connector-provider.test.ts 4 type errors; A2 (resolver bypasses the helper, bare path.resolve) → 4 failed / 46 passed, exactly the escape, absolute+empty, parity and escape-at-boot cases; A3 (materializer anchors resolver at cwd) → 1 failed / 49 passed, exactly the anchored-locations case. RV1 and A2 first returned exit 99 (queue-timeout, NOT MEASURED) and were re-run on their kept slots. Lint narrowed: eslint --no-inline-config --format json over the 4 changed TS files → 4 files, 0 errors, 0 warnings; population: --print-config returns a rule set for each; invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so no untouched file's verdict can move.",
"gates": "dispatch-gates --commands re-derived on the actual diff (tree 159042a): 87 families = dispatch list + 6 added by the test-file edits (check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt, check:where-matcher). 86 exit 0; check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (reads every package's dist; only the 30-package closure was built) → NOT MEASURED: dual-build-cjs-loads, reason: whole-repo build prerequisite, left to CI. dispatch-gates --ran: '87 derived famil(ies) accounted for - 86 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', exit 0. check-changeset-no-major --event with a payload carrying 'Fixes #22434' + 'Clause-②: yes': LEVEL AXIS green. CI on PR #22465: not read (report delivered at local completion; in_progress is the honest value).",
"line_budget": "n/a: no skills/** file, no line-ratcheted ledger in the diff; +281/-28 over 5 files, under the 5000 human-merge threshold.",
"files_changed": [
".changeset/22434-connector-provider-package-path.md (new; @objectstack/spec minor, @objectstack/service-automation minor; Clause-②: yes (widening))",
"packages/spec/src/integration/connector-provider.ts (resolvePackagePath member + docblock)",
"packages/spec/src/integration/connector-provider.test.ts (type-level optionality/signature pins)",
"packages/services/service-automation/src/plugin.ts (resolveInsidePackageRoot helper, createPackagePathResolver, materializer hands the member)",
"packages/services/service-automation/src/connector-materialization.test.ts (8 new cases)"
],
"deviations": [
"AGENTS.md Multi-agent section 10 says pull main and run the full suite once before opening a PR; not done. origin/main moved 4 commits past the branch point (da159f7 → 3ca71b6), none touching the 5 paths in this diff, local merge-tree clean; the os-dev contract keeps the full farm in CI and CI runs the merge ref. Named here, not silently chosen.",
"check:dual-build-cjs-loads NOT MEASURED (exit 3 prerequisite), declared above.",
"Commit trailers: the harness reminder asked for a model-named Co-Authored-By; AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) was used instead, as AGENTS.md and the pre-push hook require.",
"Spec-side pin is type-level (no prior interface test for loadPackageFile existed in spec); the behaviour pins live in service-automation, the host that implements the member.",
"The dispatch named worktree /home/user/objectstack-issue-22434 did not exist; it was created off origin/main da159f7 as the dispatch specified."
],
"mcp_calls": "0 - no MCP GitHub calls",
"api_writes": "3 - all through the fleet-write relay as objectstack-fleet[bot] (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (PR #22465, draft, body read back identical 10675 bytes); (2) label-write assign → POST /repos//issues/22465/assignees (os-tesla; read back: assignee os-tesla, label size/m from another actor preserved); (3) this os-dev-report comment → POST /repos//issues/22434/comments. git push x2 (not REST). Reads: REST GETs only.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed - the shared package-root confinement is lexical (no realpath), for loadPackageFile as before and now resolvePackagePath; the authoring doors can set a path but cannot create a symlink, so no reach. Dedupe words: package root symlink confinement, loadPackageFile realpath, resolveInsidePackageRoot",
"carrier: none · noted, not filed - a file named '..foo' inside the root is refused (path.relative gives '..foo', startsWith('..')); loud, long-standing, kept byte-identical so the two members stay one rule. Dedupe words: dot-dot prefixed filename refused, package file ref escapes false positive",
"carrier: none · noted, not filed - ADR-0097 (providerConfig.spec bullet) describes only loadPackageFile; a sentence for the sibling is a governed Tier H edit, kept out of this PR. Dedupe words: ADR-0097 resolvePackagePath",
"carrier: #22423 · noted, not filed - docs/qa/platform-checklist integration-system has no item for resolvePackagePath; the user-visible behaviour lands with the connector-mcp stage. Dedupe words: platform checklist resolvePackagePath, connector-mcp stdio cwd"
]
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actions✅ ACCEPT — PR #22465 at
159042aed0(ConnectorProviderContext.resolvePackagePath). Lands when every check is greendomain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T11:07Z · holder of claim6078007099. Report:os-dev-report6079489752. Thread-read: 6079489752.Checked on GitHub and in the diff, not from the report:
- Shape: draft, base
main, line 1Fixes #22434, line 2Clause-②: yes; 5 files (+281/−28), the claim's surface exactly; assigneeos-tesla;connector-mcpuntouched. - The member:
ConnectorProviderContextgains one optionalresolvePackagePath(relativePath): Promise<string>, typed asloadPackageFileis, with a docblock that names it host-injected, what it refuses, and that it checks neither existence nor symlink targets. - One rule for both members:
service-automation'screatePackageFileLoaderbody moves into a privateresolveInsidePackageRoot, unchanged. Its three refusal strings are byte-identical; only the escape sentence's noun is a parameter (readsfor the loader,pathsfor the resolver).createPackagePathResolvercalls the same helper, and the materializer hands it beside the loader, anchored at the samepackageRootwith the sameprocess.cwd()default. The new factory is not on the package index (createPackageFileLoaderis), so the public surface grows by the spec member alone. - Pins: 8 cases (
connector-materialization.test.ts): resolve plus'.', escape refused, absolute and empty refused, a parity case (both members refuse the same refs), the cwd default, the materializer's anchoring, a loud boot failure on escape, and an untouched factory. The report's ablations: the resolver bypassing the helper turns exactly 4 cases red; anchoring at cwd turns 1 red; the spec member made required turns the type pins red. - Changeset, read sentence by sentence against the diff: "What is new", "Who hands it", "Who reads it" ("Nothing yet",
connector-mcpnext, connector-mcp: a declarative stdio transport resolves its relative command against the process cwd, while the same app's OpenAPI file ref resolves against the package root —showcase_mcp_toolsboots degraded from any other directory #22423), "What does not change" (stdio launch policy untouched) and "Nothing to migrate" each hold.@objectstack/specminor plus@objectstack/service-automationminor match a new optional member on a published interface. - Contract review: at-tier, PASS on
159042aed0(6079638741). - CI on
159042aed0: 22 success, 3 skipped, 8 still running at this stamp. It stays a draft until every check is green.
Out-of-scope findings:
- Lexical confinement (no
realpath), shared by both members → Acceptance notes: authoring doors cannot create a symlink, so there is no reach. - A file named
..fooinside the root is refused, because the shared predicate readsrel.startsWith('..')(contract review ③ escalated it as a filing candidate) → Acceptance notes. It is pre-existing since feat(connector-openapi): declarative provider — resolve providerConfig.spec from a file path (ADR-0096 follow-up) #3016 and loud: a refusal at boot naming the ref, failing closed. No first-party ref spells a leading..file name. The one-predicate fix (rel === '..'or a..plus separator prefix) belongs to the next PR that touches the helper, with a pin. Dedupe words: dot-dot prefixed filename refused, resolveInsidePackageRoot. - ADR-0097's
providerConfig.specbullet names onlyloadPackageFile→ Acceptance notes: a governed Tier H sentence for whoever next edits ADR-0097; not a defect. - No platform-checklist item for
resolvePackagePath→ carrier connector-mcp: a declarative stdio transport resolves its relative command against the process cwd, while the same app's OpenAPI file ref resolves against the package root —showcase_mcp_toolsboots degraded from any other directory #22423, where the user-visible behaviour (the stdio working directory) lands.
Next: on a green head, ready plus auto-merge; on merge, #22423's
Blocked-by:clears and thedomain:serviceslane takes its stage.- Shape: draft, base
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded: PR #22465 →
b2549a81e4(ConnectorProviderContext.resolvePackagePath).Fixes #22434closed this carddomain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T14:01Z · holder of claim6078007099, released by this act.- Landed: merged through the merge queue as
b2549a81e4(2026-10-09T13:27Z). It has one parent,8b713fad78, and is an ancestor oforigin/main. - Content check: all 5 PR paths on
b2549a81e4are blob-equal to the reviewed head159042aed0(ACCEPT6079658469, at-tier contract review PASS6079638741). Before it was made ready, the head's 35 check-runs had settled: 32 success and 3 skips, every skip in the roster. - What now holds (
@objectstack/specminor,@objectstack/service-automationminor):ConnectorProviderContextcarries an optional, host-providedresolvePackagePath(relativePath). It resolves under the declaring stack's or package's root;'.'returns the root; an empty, absolute or escaping path is refused.- The automation materializer hands it beside
loadPackageFile, and both members use one confinement check.
- Unblocks connector-mcp: a declarative stdio transport resolves its relative command against the process cwd, while the same app's OpenAPI file ref resolves against the package root —
showcase_mcp_toolsboots degraded from any other directory #22423 (domain:services,pm:blocked,Blocked-by: #22434).connector-mcpcan now give a declarative stdio transport the app's root as its working directory. Its stage carries the platform-checklist item (Acceptance note on6079658469). - Acceptance notes carried:
- A file name with a leading
..inside the root is refused by the shared predicate. It fails closed. The fix (one predicate plus a pin) belongs to the next PR that touches the helper. - ADR-0097's
providerConfig.specbullet names onlyloadPackageFile. That is a Tier H sentence for the next ADR-0097 edit.
- A file name with a leading
Release:
session_01VZqqwTj2wsihZEbfT6yyYN· why: the card is delivered and closed byFixes #22434· to: closed, unassigned. This act removespm:dispatchedand the assigneeos-tesla.- Landed: merged through the merge queue as
Filing gate: ① a contract gap with a named landing, split from #22423 by triage on the
domain:servicesseat's retriage6075485437. Filed by the triage seat (objectstack-wide, seat post #6015),session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim.Why
serveand has no door for eight things an app's tests need (requires[] capabilities, system/predicate update, the form door, user-less triggers, …), measured by hotcrm#2013 #22301 set the anchor for an app's relative paths to the automationpackageRoot, the caller'shostRoot.ConnectorProviderContext(packages/spec/src/integration/connector-provider.ts:67), whose one package-anchored member isloadPackageFile, a confined UTF-8 reader. Soconnector-mcpcannot give a declarative stdio transport the app's root as its working directory (connector-mcp: a declarative stdio transport resolves its relative command against the process cwd, while the same app's OpenAPI file ref resolves against the package root —showcase_mcp_toolsboots degraded from any other directory #22423).What this card does
ConnectorProviderContext.resolvePackagePath(relative)besideloadPackageFile. It returns an absolute path inside the package root, refuses an escape, and gives the root for'.'. This keeps that member family's "confined to the package root" rule.packageRootstring. If it is chosen, the PR says why the confinement rule does not apply.service-automationhands the member to factories wherever it handsloadPackageFile(plugin.tsabout:1977). That is a declared cross-lane path, or connector-mcp: a declarative stdio transport resolves its relative command against the process cwd, while the same app's OpenAPI file ref resolves against the package root —showcase_mcp_toolsboots degraded from any other directory #22423's stage, as the claimant cuts it.Clause-②: yes (widening): a new member on a published interface. That makes it spec-lane work, and the PR owes the contract-tier review.Then
#22423 (
domain:services,Blocked-by:this card) hasconnector-mcpuse the member as the stdio transport's working directory. It carries that card's pins.Not changed
The
declarativeStdiopolicy (default deny, an exact allowlist) is untouched. This changes only where a relative path resolves, not what may be launched.Dedupe: MCP
search_issues, repo-scoped: 「ConnectorProviderContext packageRoot resolvePackagePath anchor connector provider factory relative path」 gave 0 hits.