Repository navigation
Description
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Filing gate: ② a gap measured beside a ruled card. Triage's contract review of PR #27656 (#27614), comment 6504200509, second should-fix ("follow-up, shared with the three sibling refusals; not owed by this PR"). Filed by domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP. ⛔ Not a claim. No grade: triage sets the lane and grade.
Business reason. An author who writes a datasource in code (defineDatasource) with an inline password is refused, and the refusal tells them to bind a secret on the Setup → Datasources form or to use "a handle the secret store minted". A code-defined datasource is read-only to the admin service, so the form cannot bind a secret onto it, and nothing says how a code author mints a sys_secret: handle. The author has a correct refusal and no path they can follow.
Measured (by the review, on PR #27656 head 68b2eec2)
- The four credential refusals prescribe the same two routes in the same words:
INLINE_CREDENTIAL_REFUSED,URL_EMBEDDED_CREDENTIAL_REFUSED,refusedInlineCredentialKey().describe()and the two strings fix(spec): datasource password refusals prescribe a credential shape that parses #27656 adds (packages/spec/src/data/datasource.zod.ts). The first two were maintainer-ruled in this shape ([security]sys_metadata.metadatais a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasourceconfig.password, connectorauthentication) #7990, [Decision] URL-embedded credentials (user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082). - A code-defined datasource is read-only to the admin service (
datasource-admin-service.ts:15,:656-658,:702).
Wanted
- A measured answer to "how does a code author bind a credential to a
defineDatasource?" (for example a CLI or API that mints asys_secret:handle, an env-backed resolver, or binding at deploy), then one prescription that names it, shared by all four refusals andcontent/docs/data-modeling/drivers.mdx/external-datasources.mdx. If the answer changes a ruled prescription ([security]sys_metadata.metadatais a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasourceconfig.password, connectorauthentication) #7990, [Decision] URL-embedded credentials (user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082), it goes to the maintainer as a decision card first.
Dedupe
Open issues searched for "defineDatasource credentialsRef code-defined read-only secret": none the same.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 错的必须被响亮拒绝并给处方 | none | P②
Triage: p2 ·
domain:spec·area:api· measure first · 2026-10-29T18:53Z⛔ Not a claim.
defineDatasourcetoday: a door that mints asys_secret:handle, an env-backed resolver, or binding at deploy.sys_metadata.metadatais a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasourceconfig.password, connectorauthentication) #7990, [Decision] URL-embedded credentials (user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082), or needs a new door, the seat stops and files a decision card. Adding a credential door is a security boundary.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Claim: PM loop round 3 · 2026-10-29T19:14Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-pu3xwn(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-27687-<slug>)Worktree:
objectstack-27687Domain:
domain:specSeat:
domain:spec#3Ruling-ref: triage 6505611552 (p2,
domain:spec, measure first; one shared prescription if an existing path answers; ⛔ a new door or a change to the #7990/#8082 prescription stops at a decision card)File surface:
packages/spec/src/data/datasource.zod.ts(the four credential refusals' prescription text), tests beside,content/docs/data-modeling/drivers.mdxandexternal-datasources.mdx(cross-domain exception per triage), a@objectstack/specpatch changeset; outcome B writes no code (stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT — --tier: no path-derived mandate (prose card after a measurement; no contract review unless the measurement changes a ruled prescription),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
packages/speccredential refusal prescriptions name no route adefineDatasourceauthor can follow | the UI route works for managed datasources | code authors, measured by the #27656 contract reviewThread-read: 6505611552
Premise: to be measured first by the dev on
origin/mainbfdaba43: the four credential refusals send a code author to a Setup form that cannot edit a code-defined datasourceSerial constraints cleared: none (PR #27656 merged at bfdaba4)
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufziLJNDCTSUH9pVN32p
os-dev-report
{ "issue": 27687, "status": "blocked", "branch": "claude/project-thread-pu3xwn", "pr": null, "head": null, "session": "session_01WGufziLJNDCTSUH9pVN32p", "premise_still_valid": true, "summary": "Step 1 measured on origin/main bfdaba43: no existing path binds a credential to a code-defined (defineDatasource) datasource, so outcome B, no code. C1 mint door: none (binder.bind's only caller is admin writeSecret; create refuses a code-owned name :656-658, update refuses :702, migrateCredential refuses code origin; os secret has only orphans/rewrap). C2 borrowing a handle minted for a throwaway runtime datasource resolves but dies when that row is removed (unbind) and is a per-database id in source. C3 the shipped resolver answers undefined for any non-sys_secret ref (env:, vault:, secret:), connect gives failed-credentials; a host-injected resolver is a new resolver. C4 no per-datasource env slot (OS_DATABASE_URL feeds only default); the ADR-0062 D8 escape hatch (pre-registered driver gives already-registered) bypasses the declaration, and onEnable runs after connectDeclared (app-plugin.ts:1366 vs :1632), so only a host DriverPlugin reaches it first. C5 build-time env evaluation and placeholders are refused at the parse. Options and risks for the decision card: /mnt/project-files/seat3-orders/27687-measurement.md.", "tests": "Throwaway probe (real createDatasourceSecretBinder + DatasourceAdminService + DatasourceConnectionService, fake crypto/store) in service-datasource __tests__, run under os-verify-lock after a service-datasource^... closure build (VERDICT command-exit 0, 120s): Tests 5 passed (5), VERDICT command-exit 0; deleted, git status clean.", "gates": "NOT MEASURED: outcome B writes no code, so no derived gate family reads a changed file.", "dogfood_battery": "n/a: no PR", "mcp_calls": "0", "api_writes": "1: this os-dev-report comment on #27687 via fleet-write relay", "open_questions": [ { "question": "How should a defineDatasource author bind a credential, given no existing door?", "options": [ "1 prose: the form route is for UI-created datasources; a code author who needs a password creates that datasource in Setup instead (measure code objects binding to a UI-created datasource across restart first)", "2 prose: prescribe the ADR-0062 D8 escape hatch (credential-free declaration + host DriverPlugin from process.env); adds a mechanism #7990 did not list, declaration config turns decorative, no reach into published packages", "3 new door: mint + bind a sys_secret handle for a code datasource per environment (CLI or REST, runtime overlay keyed by name); security boundary", "4 new env-backed resolver (credentialsRef env:NAME, allow-listed prefix); authored metadata reads host env, exfiltration risk on multi-tenant hosts" ], "recommendation": "1 after its measurement, or 3; the maintainer chooses between 'code authors do not declare credentialed datasources' and 'build the door'" } ], "out_of_scope_findings": [ "class: a · reach: named producer · service-datasource planCredentialMigration code-origin remedy (datasource-credential-migration.ts:165-170) says 'or supply it from the runtime environment', which no shipped host offers a named code datasource (probe C4) · same family as this card, rides its decision · dedupe: migrateCredential code-origin remedy runtime environment" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Measured: outcome B, so this card is with the maintainer ·
domain:specseat 3 · 2026-10-29T19:24Z · ⛔ not a claimThe dev's Step 1 measurement (6506246359) finds no existing path that binds a credential to a code-defined datasource on
origin/mainbfdaba43. No door mints asys_secret:handle for a code-origin datasource. The shipped resolver reads onlysys_secret:. There is no per-datasource env slot. Build-time injection is refused at the parse. Per triage 6505611552, no code is written, and the four options are with the maintainer as a decision:sys_secret:handle per environment.The same family has a fifth string:
planCredentialMigration's code-origin remedy (datasource-credential-migration.ts:165-170) says "or supply it from the runtime environment", which no shipped host offers. It moves with whichever option is chosen.objectstack-fleet commentedon Oct 30, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Decision options amended: option 5 added, and the seat now recommends it · 2026-10-30T10:03Z
The maintainer asked why a code datasource doesn't simply read an environment variable, the usual convention. It can't today because
process.envis read when the config is evaluated at build time. The value then lands in the compiled artifact, and the inline-credential refusal stops exactly that (measurement C5:config.password: password is a credential and is not accepted inline…). The convention works only if the variable is read at connect time.Option 5: a derived runtime env slot, read at connect. A code-defined datasource
warehouseresolves its password fromOS_DATASOURCE_WAREHOUSE_PASSWORDwhen it connects. The platform derives that name from the datasource name, the same pattern asOS_DATABASE_URLfordefault(the #8082 runtime-environment carve-out).credentialsRefresolve,datasource-connection-service.ts~:596-608) and where the code-origin flag is readable at connect are being measured read-only (round 3 of the measurement).The seat's recommendation is now option 5. The earlier options stand as alternatives: 1 (prose only), 3 (a per-environment bind door) and 2 (the D8 escape hatch). Option 4 (
credentialsRef: 'env:NAME') is dropped, because option 5 gives the same convenience without its risk. The chat decision card was replaced to match.objectstack-fleet commentedon Oct 30, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Option 5 scoped after measurement round 3; the seat's recommendation is now 5 + 3 · 2026-10-30T10:06Z
Round 3 (code read on
bfdaba43, appended to the measurement):connect()beside thecredentialsRefresolve (datasource-connection-service.ts:596-608). The gate must not trustrecord.origin, whichconnectDeclareddefaults to'code'at :382. It should readcontext.trigger === 'declared-auto'plusCODE_DATASOURCE_NAMES_SERVICE.has(name), a set the connection service does not read today. A UI- or API-created datasource of the same name connects throughregisterPoolwith triggerruntime-admin, so it never reads the slot.os start/os serve), option 5 works: the operator sets the variable in production. When the package is installed through the marketplace install path, its datasources are never connected at all, so no credential path reaches them. Filed separately as marketplace install-local: a package's declared datasources are never connected or registered as code-defined, so objects bound to them have no driver after install #28039.The recommendation changes accordingly. Option 5 should be on for single-tenant and self-hosted hosts and off by default on multi-tenant ones. Option 3 (a per-environment bind door) is the safe route for multi-tenant cloud, and goes on its own card. The chat decision card was replaced again to carry this scope.
objectstack-fleet commentedon Oct 30, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Maintainer ruling: option 5 · 2026-10-30T15:03Z
At 2026-10-30T15:02Z the maintainer chose option 5 in the project chat (「选项五」), from the decision whose options are recorded above (6520085549, 6520139448). The ruling: a code-defined datasource reads its credential at connect time from a runtime environment variable whose name the platform derives from the datasource name. It reads
OS_DATASOURCE_<NAME>_PASSWORD, the same pattern asOS_DATABASE_URLfordefault.The ruling chose option 5 alone, not 5 + 3. The per-environment bind door (option 3) is not ruled, and it stays an open idea for multi-tenant cloud. The marketplace install route is #28039.
The guards that come with option 5, as recorded in 6520139448 and the measurement's round 3:
CODE_DATASOURCE_NAMES_SERVICEplus the connect triggerdeclared-auto, never the caller-suppliedrecord.origin. A UI- or API-created datasource of the same name never reads it.OS_DATASOURCE_ENV_CREDENTIALS. It is on for the single-artifact CLI host (os start/os serve) and off in every other composition unless the operator sets it toon. The dev measures the seam and states it in the PR.external.credentialsRefkeeps precedence, and the variable's value never reaches stored metadata, logs or an answer.This card moves from measurement to build under the same claim, re-scoped in the next comment. A contract review is owed, because this is a new credential resolution path.
objectstack-fleet commentedon Oct 30, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Claim amendment: from measurement to build, under the option 5 ruling ·
domain:specseat 3 · sessionsession_01WGufzgbJhFoXYsLSe5WSPP· 2026-10-30T15:04Z · same claim 6506069472, same branchclaude/project-thread-pu3xwnpackages/services/service-datasource/**: the env slot inconnect()beside thecredentialsRefresolve, gated on triggerdeclared-autoplusCODE_DATASOURCE_NAMES_SERVICE, which is passed in through the admin plugin's wiring. It also coversplanCredentialMigration's code-origin remedy, which then names the variable.packages/runtime/**andpackages/cli/**, only as far as the host switch's seam needs.packages/spec/src/data/datasource.zod.ts, plus the twopasswordguidance strings, which add the code author's route.content/docs/data-modeling/drivers.mdxandexternal-datasources.mdx, and tests beside each.@objectstack/service-datasourceminor,@objectstack/specpatch, and runtime / cli if they move.docs/adr/**orskills/**(governed); name any text there that this makes stale. No change to the marketplace install path (marketplace install-local: a package's declared datasources are never connected or registered as code-defined, so objects bound to them have no driver after install #28039).M,mode:thread,effort: high. A contract review is owed (triage's).failed-credentialsnow connects when the slot is set. Nothing accepted before is refused.OS_DATASOURCE_ENV_CREDENTIALS(on/off). It defaults on in the single-artifact CLI host and off in every other composition. Measure the seam first; if the CLI host cannot be told apart from a library composition without a new option, stop and report.objectstack-fleet commentedon Oct 30, 2026
domain:spec#3 · os-support-ai · session_01WGufziLJNDCTSUH9pVN32p
os-dev-report #27687 (build round)
{ "issue": 27687, "status": "done", "branch": "claude/project-thread-pu3xwn", "pr": "https://github.com/objectstack-ai/objectstack/pull/28208", "head": "5eda07f1ec", "session": "session_01WGufziLJNDCTSUH9pVN32p", "premise_still_valid": true, "summary": "Option 5 as ruled. DatasourceConnectionService.connect() reads OS_DATASOURCE_<NAME>_PASSWORD (name upper-cased, non-[A-Z0-9] -> _) after the credentialsRef and connector steps, only when neither is declared, not asDefault, the envCredentials slot is configured (switch on), trigger is declared-auto and the name is in the code-datasource provenance set (never record.origin). Switch OS_DATASOURCE_ENV_CREDENTIALS on/off, other values throw at the admin plugin's init (boot refused). os serve stamps 'on' when unset just before building the admin plugin (seam a; os start/os dev spawn serve); library default off. Install-local (#28132) reads it too (trust consequence stated in docs/body, pinned). Unset variable: verdict stays the driver's failed-degraded, reason names the variable. Collision refused failed-credentials. Shared sentence CODE_DEFINED_CREDENTIAL_ROUTE appended to 5 refusals + refusedInlineCredentialKey describe + 2 password guidance strings; planCredentialMigration code remedy names the variable; external-datasources.mdx section 4 and drivers.mdx teach it.", "tests": "service-datasource datasource-env-credential.test.ts 24/24; runtime datasource-env-credential.test.ts 3/3 (real admin plugin + real driver factory, incl. connectPackageDatasources installed:true); cli datasource-env-credentials-default.test.ts 4/4; spec code-defined-credential-route.test.ts 16/16; migration test pin. Package runs: spec typecheck/test(757 files)/test:repo(87) 0; service-datasource typecheck/test(66 files,1093) 0; runtime typecheck/test(486 files) 0; cli typecheck + --project unit(354 files) 0; pnpm --filter '...^@objectstack/service-datasource' typecheck (27 pkgs) 0. 12 ablations via ablation-replace.mjs, each red then restored blob==HEAD (A10 through rebuilt dist).", "gates": "dispatch-gates --commands: 110; all run; 4 first exited 3 (shallow clone, unbuilt dists), rerun 0 after git fetch --unshallow + pnpm build (115/115); --ran: 110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN. check:generated --fix regenerated api-surface, export-origins, 4 driver reference pages. check-nul-bytes OK.", "dogfood_battery": "not run (widening; no runtime src change; serve.ts stamp only)", "mcp_calls": "subscribe_pr_activity #28208 only", "api_writes": "relay pr_create #28208 (16618 bytes sent = stored, no assignee/label) + this comment; git push x1", "open_questions": [], "deviations": [ "Changesets are patch on all three packages, not service-datasource/cli minor: check-changeset-no-major refuses minor beside Metadata-protocol: none.", "Unset variable keeps the existing driver verdict failed-degraded (order said failed-credentials); only the reason gains the variable.", "Five refusals carry the sentence (PASSTHROUGH_NESTED_CREDENTIAL_REFUSED too) plus the refusedInlineCredentialKey describe, which moved 4 generated driver reference pages.", "Collision refused at its connect (failed-credentials), not a separate boot pass; schema-valid names cannot collide.", "Commit trailers reworded to the model-free pair before the first push (pre-push check:commit-card-trailers); tree unchanged." ], "stale_governed_text": [ "skills/objectstack-data/rules/datasources.md:63 'Credentials — one channel'", "docs/adr/0015 section 10 (:764) and D7 note (:10)", "docs/adr/0062 D3 (:92)" ], "out_of_scope_findings": [ "Plugin drivers resolving credentialsRef per request (OData via DATASOURCE_SECRET_RESOLVER_SERVICE) do not read the slot; documented as a limit." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
ACCEPT → PR #28208 comment 6538052081
objectstack-fleet commentedon Oct 31, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-31T03:21Z5eda07f1e, merge commite063a87e61origin/maingit merge-base --is-ancestor)Delivered:
defineDatasource) reads its password at connect fromOS_DATASOURCE_<NAME>_PASSWORD. The variable name is derived from the datasource name, and the value never enters the artifact, stored metadata, logs or an answer.declared-auto. A declaredcredentialsRefor connector wins. The slot sits behindOS_DATASOURCE_ENV_CREDENTIALS, which is on inos serve/os startand off in library compositions.Owed elsewhere: #28283 (S1 and S2 of the review; a ready patch is noted there) and #28209 (the skills, ADR-0015 and ADR-0062 text, now unblocked).
pm:dispatchedis removed here; the card is closed by the merge.