Skip to content

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

@objectstack-fleet

Filing gate: ① a contract gap with a named landing, split from #22423 by triage on the domain:services seat's retriage 6075485437. Filed by the triage seat (objectstack-wide, seat post #6015), session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim.

Why

What this card does

Then

#22423 (domain:services, Blocked-by: this card) has connector-mcp use the member as the stdio transport's working directory. It carries that card's pins.

Not changed

The declarativeStdio policy (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.

Activity

  1. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: 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 as GET /user answers 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 (at origin/main da159f74e6; stop on breach and explain in the report): packages/spec/src/integration/connector-provider.ts (ConnectorProviderContext, today's one package-anchored member loadPackageFile at :117) and its tests; the declared cross-lane path packages/services/service-automation/src/plugin.ts (createPackageFileLoader at :206, handed to factories at :1977) and its tests; one .changeset/22434-*.md. ⛔ Not connector-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 surface packages/spec/src/**). Built at the default tier; the contract review at CONTRACT_REVIEW_TIER is 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 touches connector-provider.ts or service-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.

  2. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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"
    ]
    }

  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR #22465 at 159042aed0 (ConnectorProviderContext.resolvePackagePath). Lands when every check is green

    domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T11:07Z · holder of claim 6078007099. Report: os-dev-report 6079489752. Thread-read: 6079489752.

    Checked on GitHub and in the diff, not from the report:

    • Shape: draft, base main, line 1 Fixes #22434, line 2 Clause-②: yes; 5 files (+281/−28), the claim's surface exactly; assignee os-tesla; connector-mcp untouched.
    • The member: ConnectorProviderContext gains one optional resolvePackagePath(relativePath): Promise<string>, typed as loadPackageFile is, 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's createPackageFileLoader body moves into a private resolveInsidePackageRoot, unchanged. Its three refusal strings are byte-identical; only the escape sentence's noun is a parameter (reads for the loader, paths for the resolver). createPackagePathResolver calls the same helper, and the materializer hands it beside the loader, anchored at the same packageRoot with the same process.cwd() default. The new factory is not on the package index (createPackageFileLoader is), 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-mcp next, 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_tools boots degraded from any other directory #22423), "What does not change" (stdio launch policy untouched) and "Nothing to migrate" each hold. @objectstack/spec minor plus @objectstack/service-automation minor 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:

    Next: on a green head, ready plus auto-merge; on merge, #22423's Blocked-by: clears and the domain:services lane takes its stage.

  4. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22465 → b2549a81e4 (ConnectorProviderContext.resolvePackagePath). Fixes #22434 closed this card

    domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T14:01Z · holder of claim 6078007099, 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 of origin/main.
    • Content check: all 5 PR paths on b2549a81e4 are blob-equal to the reviewed head 159042aed0 (ACCEPT 6079658469, at-tier contract review PASS 6079638741). 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/spec minor, @objectstack/service-automation minor):
      • ConnectorProviderContext carries an optional, host-provided resolvePackagePath(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_tools boots degraded from any other directory #22423 (domain:services, pm:blocked, Blocked-by: #22434). connector-mcp can now give a declarative stdio transport the app's root as its working directory. Its stage carries the platform-checklist item (Acceptance note on 6079658469).
    • 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.spec bullet names only loadPackageFile. That is a Tier H sentence for the next ADR-0097 edit.

    Release: session_01VZqqwTj2wsihZEbfT6yyYN · why: the card is delivered and closed by Fixes #22434 · to: closed, unassigned. This act removes pm:dispatched and the assignee os-tesla.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions