Skip to content

os serve: a stack's plugin driver resolves its datasource's external.credentialsRef through the host's datasource secret binder (driver-odata on os serve) — epic #27261 P2 #27875

Closed

Description

@objectstack-fleet

epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Filing gate: ① gap found by the #27747 guide carrier (PR #27871, os-dev-report 6513440511) and measured on origin/main.
Ruling-ref: 6486145005 (#27299, Tier B = declarative OData)
Dedupe (open AND closed): "os serve secret binder plugin driver credentialsRef sys_secret" → none this work.
Reader: the epic PM (#27261) dispatches it in the epic subtree.
type: Feature

维护者速读

  • 做什么: os serve 会创建数据源密钥解析器(createDatasourceSecretBinder,packages/cli/src/commands/serve.ts 约 5310 行),但只交给 DatasourceAdminServicePlugin。栈里自己装的插件驱动(比如 createODataDriverPlugin)拿不到它,于是 credentialsRef: 'sys_secret:…' 在 os serve 上解析不了,作者只能自己传一个 resolve。
  • 为什么: 声明式 OData 的卖点是"只写元数据不写代码"。凭据这一步要写代码,卖点就破了。
  • 需要您做的: 无。

Task

  • The host publishes the read half of its datasource secret binder (resolve(credentialsRef)) as a kernel service. driver-odata's plugin uses that service when options.secrets is absent; an explicit options.secrets still wins. Name the service and say whether other plugin drivers should read it the same way.
  • Fail-closed stays: no service and no options.secrets ⇒ the request is refused, never sent unauthenticated.
  • Tests: on a host that registers the service, a sys_secret: handle resolves; without it, the refusal is unchanged.
  • Update content/docs/data-modeling/odata-datasources.mdx (docs(data-modeling): an OData datasources guide, and the three-way choice between declarative OData, a plugin driver and a synced real object #27871): drop the "pass your own resolve on os serve" caveat.

Clause-②: decided by the dev from the diff · Metadata-protocol: none.

Activity

  1. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Claim: PM loop round 12 2026-10-30T02:29Z
    Session: session_017ihL2c5z3q4h8inrn5kwqV (epic PM); implementing card thread session_017ihL2fy9PSGEZJdJpoPvzx
    Account: marchtian
    Branch: claude/project-thread-vfnybu (the card thread's platform-assigned branch — measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-27875
    Domain: domain:server
    Seat: epic:#27261#1 (epic PM; no domain:* seat). Maintainer order, 田騏 2026-10-29T21:58Z: 「现在启动全部 P2」 (ruling 6486145005, Tier B = declarative OData)
    File surface: packages/cli/src/commands/serve.ts (publishing the binder's read half as a kernel service), packages/services/service-datasource/src/** (only if the service name or its type is owned there), packages/types/src/** (only if the read-half type belongs beside the other cross-package seams), packages/drivers/driver-odata/src/plugin.ts (the fallback lookup), content/docs/data-modeling/odata-datasources.mdx (drop the caveat, once #27871 is on main), their tests, .changeset/** (stop on breach, including any packages/spec/src/** edit; explain in the report)
    Container & model: S, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: yes
    Metadata-protocol: none
    Responsibility: epic #27261 P2, gap found by #27747's carrier (os-dev-report 6513440511)
    Thread-read: none
    Premise: holds — on origin/main, serve.ts builds createDatasourceSecretBinder and hands it only to DatasourceAdminServicePlugin; a stack plugin driver cannot resolve a sys_secret: handle.
    Serial constraints: #27746 (PR #27819, in review) edits driver-odata/src/plugin.ts and transport.ts. Merge main after it lands before touching plugin.ts, or keep the edit to the secrets fallback and say how the two meet. #27871 (#27747) owns odata-datasources.mdx until it lands. An explicit options.secrets still wins, and fail-closed is unchanged.

  2. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2fy9PSGEZJdJpoPvzx
    os-dev-report

    {
     "issue": 27875,
     "status": "done",
     "branch": "claude/project-thread-vfnybu",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/27952",
     "head": "33bc41fb",
     "session": "session_017ihL2fy9PSGEZJdJpoPvzx",
     "premise_still_valid": true,
     "summary": "Service: DATASOURCE_SECRET_RESOLVER_SERVICE = 'datasource-secret-resolver' with IDatasourceSecretResolver { resolve(credentialsRef) } in packages/types/src/datasource-secret-resolver.ts; the module says every plugin driver dereferencing a credentialsRef reads it the same way (explicit store first, service at the first request never at init, fail-closed). Producer: createDatasourceSecretResolverPlugin(secrets) in cli serve.ts (providesServices the service), mounted after DatasourceAdminServicePlugin only when a binder was built; publishes resolve only, decrypts the datasource_credential scope only. Consumer: driver-odata plugin.ts credential() uses secrets ?? hostSecretResolver(ctx); the kernel's getService throw is caught so the refusal is the plugin's own, now naming the service. Guide: #27871 landed mid-card, so odata-datasources.mdx drops the os serve caveat (mount example without secrets, option row, Credentials section, fail-closed list). Changeset patch on types, cli, driver-odata.",
     "tests": "New: cli serve-datasource-secret-resolver.test.ts (5: real ObjectKernel over a real binder with LocalCryptoProvider; bound credential resolves via the service; service keys are only resolve; a settings-scoped row, a missing row and a foreign handle answer undefined; providesServices; source pin of the if (secrets) mount). driver-odata plugin.test.ts (5 new: resolves via the host service, asked once; explicit options.secrets wins and the service is never asked; unresolved handle refused with nothing sent; a service registered after init is seen; on a real kernel with neither, the plugin's own refusal, no kernel text, nothing sent). Ablations each red then restored: fallback removed 3 failed, precedence flipped 1, try/catch removed 1, whole binder registered 1. On 33bc41fb after a full build: driver-odata 720 passed, types 817, cli serve-datasource-* 11; typecheck exit 0 for types, driver-odata, cli.",
     "gates": "dispatch-gates --commands on 33bc41fb derived 112 families; all run with exit codes recorded before any pipe; --ran: 112 derived, 112 run, 0 NOT-MEASURED (a DERIVED zero). An earlier pass was red on 3 gates about the new test double (engine-double-contract, where-matcher, cross-package-test-inputs), all fixed in the diff. Check Changeset run locally against the PR body: exit 0. Full battery is CI's.",
     "dogfood_battery": "n/a: no ! in the title, no (narrowing) arm",
     "mcp_calls": "github issue_read x2 (#27875 get, get_comments), pull_request_read x2 (#27871 get, #27819 get), subscribe_pr_activity x1 (#27952); no MCP write tool",
     "api_writes": "2: relay pr_create (run 41005139498, PR #27952, body 10818 bytes read back identical, assignee marchtian); relay comment on #27875 (this report). git push is not a REST write.",
     "surface_note": "Outside the claim's file list: scripts/engine-double-contract.pinned.json (the ledger row check:engine-double-contract requires for the new test double; coverage only grows) and pnpm-lock.yaml (+3, the @objectstack/types dependency of driver-odata). No packages/spec/src edit. Worktree is objectstack-27875, not objectstack-issue-27875.",
     "serial_constraints": "#27819 (#27746): this PR touches only the if (!secrets) block in credential() plus one helper and one import in plugin.ts, so the two meet as a small textual conflict there; the package.json and lockfile lines for @objectstack/types are identical in both. Whichever lands second resolves it.",
     "open_questions": [],
     "out_of_scope_findings": [
      "carrier: whichever of #27952 / #27819 lands second · packages/drivers/driver-odata/README.md mount example still passes secrets: datasourceSecretBinder (still correct, now optional under os serve); one-line follow-up, README outside this claim",
      "note: a stack that registers its own datasource-secret-resolver service collides loudly at boot with os serve's (Service already registered); os serve mounts its plugin only when it mounts the datasource admin itself"
     ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Landed · 2026-10-30T06:34Z

    PR #27952 merged through the queue as 0a52b7dd on 2026-10-30T06:19:49Z. Head was cf00041b. The contract review PASS is 6515948918 at TIER_DEFAULT, on the declaration face, and the seat ACCEPT followed it. Under os serve, a plugin driver's datasource credentialsRef now resolves through the server's own sys_secret store, using the kernel service datasource-secret-resolver (@objectstack/types). An OData stack needs no secrets option. The driver-odata README and the OData guide say so.

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions