Uh oh!
There was an error while loading. Please reload this page.
feat(spec): refuse ${…} placeholder syntax in connection-material driver config keys at publish (#8336) - #8457
Conversation
…ver config keys at publish (#8336) The #7990 census (measured during #8078) established that ${…} placeholders in authored datasource config are resolved by nothing — stored verbatim in sys_metadata, handed verbatim to the database client, failing at a distance with no error naming the placeholder. Two shipped refusal messages (#8078, #8082) had to warn around the broken escape. Maintainer-ruled direction 2 (2026-08-13): refuse the syntax loudly at publish; direction 1 (implement resolution) rejected — a capability with an env-exfiltration surface and no measured pull. - One shared value-level judgement in data/driver/common.zod.ts (UNRESOLVED_PLACEHOLDER_REFUSED / containsUnresolvedPlaceholder / placeholderFree / placeholderFreeDeep), the #8082 single-mechanism construction, applied to every connection-material string key the shared factory hands to the client: postgres url/host/database/username/schema/ applicationName, mysql url/host/database/username, mongo url/host/database/ username/authSource + the options passthrough (judged deep), turso url/syncUrl/encryptionKey, sqlite + sqlite-wasm filename. - Boundary: placeholder-by-intent — complete ${…} spans only; $VAR, {name}, unclosed ${ stay accepted; no-contract drivers stay unjudged (#4410 line). - The #8082 message now points at the refusal instead of warning around it. - #7990-census acceptance pins inverted, not deleted (datasource.test.ts, postgres.test.ts); family pins in driver-placeholder-refusal.test.ts. - ADR-0087: D3 semantic entry datasource-config-placeholder-refused (no D2 — no mechanical rewrite exists); registry/spec-changes/upgrade-guide regenerated. Changeset: @objectstack/spec minor (launch-window accept-set narrowing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
gen:api-surface after rebuilding dist (the migration-registry regen had made dist older than src, which the generator refuses — #7122/#4687). Adds the four new data exports: UNRESOLVED_PLACEHOLDER_REFUSED, containsUnresolvedPlaceholder, placeholderFree, placeholderFreeDeep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
Uh oh!
There was an error while loading. Please reload this page.
…relay after #8457) Discharges the os-regen deferral from the origin/main merge: api-surface, export-origins, reference docs, strictness-ledger counts re-derived on the merged base so the union carries both #8457's entries and the #8211 synonym-gate exports. gen:openapi re-run after the chain per the script's warning. Sibling assertions green (datasource-config-placeholder-refused registered; placeholderFree/containsUnresolvedPlaceholder body present). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
Fixes#8336
Maintainer-ruled direction 2 (on-card comment, 2026-08-13):
${…}placeholder syntax in connection-material driver-config keys is refused at publish with explicit "placeholders are not resolved here" guidance pointing at the real escapes. Direction 1 (implement resolution) is rejected — no substitution is built.The defect this closes
A placeholder in authored datasource config (e.g.
config.urlofpostgresql://${DB_HOST}/db) is resolved by nothing — stored verbatim insys_metadata, handed verbatim to the database client at connect (#7990 census, measured during #8078). The author believes environment substitution happens; the connection fails (or connects somewhere unintended) with no error naming the placeholder. Until now the syntax looked supported: parsed green, stored fine, failed at a distance.What lands
UNRESOLVED_PLACEHOLDER_REFUSED+containsUnresolvedPlaceholder+placeholderFree/placeholderFreeDeepindata/driver/common.zod.ts— one shared value-level judgement, the same single-mechanism construction as [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'scredentialFreeUrl, so the policy cannot drift per driver or per key.url,host,database,username,schema,applicationNameurl,host,database,usernameurl,host,database,username,authSource, and theoptionspassthrough (judged deep — every nested string reaches the client, and the passthrough is exactly where a refusal onurl/hostwould displace the placeholder to)url,syncUrl,encryptionKeyfilenamepassword/authToken, refused whole) and the URL keys ([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:url/syncUrl, userinfo judged). The credential keys already refuse every value, placeholder included; this card extends the refusal to the rest of the connection-material class rather than only the URL keys, because partial coverage recreates the displacement [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 measured (refuse${DB_HOST}inurland the green escape becomeshost: '${DB_HOST}'). Thememorydriver is excluded: it connects to nothing, and itsinitialDatacarries arbitrary record values where a literal${…}may be legitimate data.${…}span refuses;$VAR,{name}and an unclosed${stay accepted; drivers with no shipped contract (plugin-contributed ids) keep their config unjudged — the honest datasource.config 至今无人校验:驱动 configSchema 是声明但完全惰性的(ADR-0049 enforce-or-remove,#4001 收尾发现) #4410 line.user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082 message now points through the fixed escape instead of only warning around it: "…are resolved by nothing … and are themselves refused at publish (${...}placeholders in authored metadata resolve to nothing and reach the consumer verbatim — the masked-failure escape #8078 measured, now load-bearing for two refusal messages #8336)". (The PM dispatch named two shipped messages carrying the placeholder warning; measured onorigin/main, onlyURL_EMBEDDED_CREDENTIAL_REFUSEDcarries it — [security]sys_metadata.metadatais a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasourceconfig.password, connectorauthentication) #7990'sINLINE_CREDENTIAL_REFUSEDnever mentioned placeholders, so there was nothing to update there.)sys_metadata.metadatais a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasourceconfig.password, connectorauthentication) #7990-census acceptance pins are inverted, not deleted —datasource.test.ts"environment variables in config" andpostgres.test.ts"environment variable patterns" now pin the refusal on the same inputs; therest_api/salesforceplaceholder fixtures stay accepted (no shipped contract) and are annotated as the boundary. No other fixture in the repo authors a${…}connection value (measured by grep over examples + packages).datasource-config-placeholder-refused(D3; no D2 conversion — there is no mechanical rewrite: the placeholder names a value that exists only in the author's intended deployment environment, and substituting anything would invent a connection target). Registry, spec-changes, upgrade guide regenerated. Measured stored/authored rows before refusing: zero placeholder-bearing connection configs exist in examples or fixtures beyond the two acceptance pins named above; the refusal door is publish, and stored rows replay conversions at rehydration unaffected by this entry (no conversion registered).@objectstack/specminor per the dispatch's launch-window accept-set-narrowing convention, with theadr-0087: registeredmarker.Coverage
New
driver-placeholder-refusal.test.ts: 20-key family table (refusal pathed at the key,code: 'custom', message asserts the ruling's guidance + escapes + carve-out; literal spelling accepted byte-identically; near-miss literals accepted), mongooptionsdeep pins,DatasourceSchemare-path pin, no-contract boundary pin, #8082 composition pin (a URL violating both facts reports both). Envelope note (same as the #8082 pins):statusdoes not exist at the schema layer — every schema refusal is wrapped uniformly by the publish door's422 INVALID_METADATA(pinned generically in metadata-protocol, e.g.protocol.save-flow-canonicalization.test.tsasserting{ code: 'INVALID_METADATA', status: 422 }); the zod issue'scodeand path are asserted here.Runtime-environment DSNs (
OS_DATABASE_URLand friends) never pass through this authoring schema and are unaffected by construction.🤖 Generated with Claude Code
https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
Generated by Claude Code
Generated by Claude Code