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
datasource: the credential refusals send a code author (defineDatasource) to a Setup form that cannot edit a code-defined datasource; name a binding path a code author can follow #27687
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)
Read fresh. The refusal is correct but prescribes a path a code author cannot follow.
Step 1 is a measurement, not code. The claim answers, from the tree, how a code author binds a credential to a defineDatasource today: a door that mints a sys_secret: handle, an env-backed resolver, or binding at deploy.
If an existing path answers it, the four refusals and the two docs pages name it in one shared prescription. The docs pages ride under a cross-domain exception the claim declares.
Claim: PM loop round 3 · 2026-10-29T19:14Z
Session: session_01WGufzgbJhFoXYsLSe5WSPP
Account: os-support-ai
Branch: claude/project-thread-pu3xwn (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-27687-<slug>)
Worktree: objectstack-27687
Domain: domain:spec
Seat: domain:spec#3
Ruling-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.mdx and external-datasources.mdx (cross-domain exception per triage), a @objectstack/spec patch 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: high
Clause-②: no
Metadata-protocol: none
Responsibility: packages/spec credential refusal prescriptions name no route a defineDatasource author can follow | the UI route works for managed datasources | code authors, measured by the #27656 contract review
Thread-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 datasource
Serial constraints cleared: none (PR #27656 merged at bfdaba4)
{
"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"
]
}
Measured: outcome B, so this card is with the maintainer · domain:spec seat 3 · 2026-10-29T19:24Z · ⛔ not a claim
The 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 a sys_secret: handle for a code-origin datasource. The shipped resolver reads only sys_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:
The prose says the form route is for UI-created datasources (the seat's recommendation, after measuring that code objects can bind to one across a restart).
A new door that binds a sys_secret: handle per environment.
A prose route to the ADR-0062 D8 host-driver escape hatch.
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.
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.env is 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 warehouse resolves its password from OS_DATASOURCE_WAREHOUSE_PASSWORD when it connects. The platform derives that name from the datasource name, the same pattern as OS_DATABASE_URL for default (the #8082 runtime-environment carve-out).
The metadata names no variable, so authored metadata cannot point at an arbitrary host variable. That closes option 4's exfiltration path.
Guard: the slot is honoured only for code-origin datasources, the ones that come from the package artifact. A datasource created through the UI or API under the same name never reads it. Cloud hosts inject the variable per environment, or leave it unset.
It is a new credential resolution path, so it needs the ruling. The hook point (beside the credentialsRef resolve, 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.
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):
Hook.connect() beside the credentialsRef resolve (datasource-connection-service.ts:596-608). The gate must not trust record.origin, which connectDeclared defaults to 'code' at :382. It should read context.trigger === 'declared-auto' plus CODE_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 through registerPool with trigger runtime-admin, so it never reads the slot.
Multi-tenant. Every tenant artifact booted in one process is code-origin, so a process-wide variable is readable by any tenant artifact that declares the same name. The slot must be off on multi-tenant hosts, or injected per environment by the operator's composition.
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.
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 as OS_DATABASE_URL for default.
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:
The slot is honoured only for a code-origin datasource. The test is the host's provenance set CODE_DATASOURCE_NAMES_SERVICE plus the connect trigger declared-auto, never the caller-supplied record.origin. A UI- or API-created datasource of the same name never reads it.
On a host where several tenant artifacts share one process, a process-wide variable would be readable by every tenant's artifact. So the slot is behind a host switch, 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 to on. The dev measures the seam and states it in the PR.
A declared external.credentialsRef keeps 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.
Claim amendment: from measurement to build, under the option 5 ruling · domain:spec seat 3 · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-30T15:04Z · same claim 6506069472, same branch claude/project-thread-pu3xwn
Ruling-ref: the maintainer's option 5 (ruling record 6525963988), with its guards.
File surface:
packages/services/service-datasource/**: the env slot in connect() beside the credentialsRef resolve, gated on trigger declared-auto plus CODE_DATASOURCE_NAMES_SERVICE, which is passed in through the admin plugin's wiring. It also covers planCredentialMigration's code-origin remedy, which then names the variable.
packages/runtime/** and packages/cli/**, only as far as the host switch's seam needs.
The four credential refusal strings in packages/spec/src/data/datasource.zod.ts, plus the two password guidance strings, which add the code author's route.
content/docs/data-modeling/drivers.mdx and external-datasources.mdx, and tests beside each.
Root changesets: @objectstack/service-datasource minor, @objectstack/spec patch, and runtime / cli if they move.
Container & model: M, mode:thread, effort: high. A contract review is owed (triage's).
Clause-②: yes (widening). A code-origin connect that answered failed-credentials now connects when the slot is set. Nothing accepted before is refused.
Metadata-protocol: none. No authored key, value or default moves; the refusal text change is prose.
Host switch: 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.
{
"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."
]
}
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