Skip to content

extend-existing-systems-with-ai: the datasource code sample no longer parses — config.connection, user and an inlined password are all rejected now that per-driver config is enforced #175

Closed

Description

@hotlong

Found while correcting the same post's ADR-0015 roadmap paragraph (#149). Filed unassigned. Not fixed under that card: its rulings scope it to the closing roadmap paragraph ("correct the paragraph, do not re-argue the post"), and this is a different defect class — a code sample, in all 8 locale files, that also has to pick between Datasource and defineDatasource.

What the post shows

content/blog/extend-existing-systems-with-ai/index.mdx (and every locale — 8 files carry the identical block):

import type { Datasource } from '@objectstack/spec';

export const BusinessDb: Datasource = {
  name: 'business_primary',
  driver: 'postgres',
  config: {
    connection: {                      // ← not a PostgresConfig key
      host: process.env.BIZ_DB_HOST,
      user: process.env.BIZ_DB_USER,   // ← the key is `username`
      password: process.env.BIZ_DB_PASSWORD,  // ← inlining refused
      database: process.env.BIZ_DB_NAME,
    },
  },
  pool: { min: 1, max: 10 },
  active: true,
};

What the spec now enforces

Measured on objectstack origin/main @ dbf1152:

  • DatasourceSchema runs reportDriverConfigIssues(ctx, ds.driver, ds.config, ['config']) — packages/spec/src/data/datasource.zod.ts — so config is parsed against the driver's own schema (ENFORCED as of #4410, per the header comment on packages/spec/src/data/driver/postgres.zod.ts).
  • PostgresConfigSchema is a strictObject whose keys are url / host / port / database / username / ssl / schema / applicationName / statementTimeout / autoMigrate. connection is not among them, and user is not the spelling — the nesting is exactly what the enforcement was added to stop ("a connection key written one level too high left the datasource connecting on driver defaults rather than failing").
  • password is typed never: "Set through the connection form's secret field or external.credentialsRef — encrypted into sys_secret, never stored in config." The datasource-level guidance is blunt: "password must never be inlined on a datasource."

The type import is not what fails: Datasource is still exported (packages/spec/src/data/datasource.zod.ts:807), and on the datasource schema config is declared as an unconstrained record — z.record(z.string(), z.unknown()) — so TypeScript accepts this literal happily. It fails at parse/boot, in the driver-config refinement. That gap between "compiles" and "parses" is precisely what defineDatasource (exported from the package root, packages/spec/src/index.ts:171) closes, and it is what the monorepo's own guide uses — content/docs/data-modeling/external-datasources.mdx.

Why it matters

This is the post the site sends readers to for the connect route, and the block is the first code a reader copies. It is also the exact shape an AI agent will reproduce from the corpus. The post's own sentence above it — "Credentials come from the environment, never inlined in source" — is the right instruction; the sample under it does the opposite.

Shape of the fix

Rewrite the block in the English original as defineDatasource with a flat config (host / port / database / username) and no password, then carry it into all seven translated files (zh-Hant regenerated with pnpm gen:zh-hant, never hand-edited). Note the FAQ and governance list say "bind to a read-only connection or DB user", which stays correct and must stay distinct from the federation write gate — see the closing section as corrected in the PR for #149. It is a live post, so it sets updated and never date.

Re-check

grep -rn "connection: {" content/blog/                                    # 8 files, one per locale
git -C objectstack show origin/main:packages/spec/src/data/driver/postgres.zod.ts | head -20
git -C objectstack show origin/main:content/docs/data-modeling/external-datasources.mdx | sed -n '22,50p'

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    Contributor

    repo:www#3 · os-tesla · session_01L9qibvdwLLfobyXipfCGq7

    Triage: lands in content/blog/extend-existing-systems-with-ai/ (all eight locale files; zh-Hant via pnpm gen:zh-hant); rationale: a published post's first copyable code sample no longer parses under the enforced per-driver datasource config, and it inlines a password right under the post's own "never inlined" sentence. That violates a declared contract, so this is a Bug, user-reachable, and it is the shape an agent will copy from the corpus.
    Path: none
    Type: Bug
    Re-check: git grep -n "connection: {" origin/main -- content/blog/extend-existing-systems-with-ai/ returned 8 hits at 6d95393 (2026-10-29T14:22Z).
    Serial note: open PR #283 (slot Wed 11-11) also edits these eight files (a back-link and its updated line). Both land; the later one resolves the conflict.
    Size/model suggestion: S, not mechanical (the dev picks the current spelling on today's npm latest and measures it), default tier, effort high.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    Contributor

    repo:www#3 · os-tesla · session_01L9qibvdwLLfobyXipfCGq7

    Claim: PM loop round 1, 2026-10-29T14:26Z
    Session: session_01L9qibvdwLLfobyXipfCGq7
    Account: os-tesla (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-175-datasource-sample
    Worktree: www.objectos.ai-issue-175
    Domain: n/a (single-lane repo)
    Seat: repo:www#3 (a defect-card seat; publishing stays with seat 1 per the 2026-10-21 ruling on #267, and this seat touches no queued post)
    File surface: content/blog/extend-existing-systems-with-ai/index*.mdx (8 files, zh-Hant regenerated) (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: TIER_DEFAULT, effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the post's own sample (this repo's content) produces the risk; the platform path that covers it is the enforced per-driver config schema plus the secret field / external.credentialsRef; every reader who copies the connect-route sample reaches it today
    Thread-read: 6499270698
    Premise: holds
    Serial constraints cleared: open PR #283 also edits these eight files (back-link + updated); ordinary concurrency, the later lander resolves the conflict


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    Contributor

    repo:www#3 · os-tesla · session_01L9qibvdwLLfobyXipfCGq7

    os-dev-report
    {
    "issue": 175,
    "status": "done",
    "branch": "claude/issue-175-datasource-sample",
    "pr": "#308",
    "session": "session_01L9qibvdwLLfobyXipfCGq7",
    "premise_still_valid": true,
    "summary": "Rewrote the step-1 datasource sample of extend-existing-systems-with-ai as defineDatasource (from @objectstack/spec/data) with a flat config (host/database/username from env), no password, and the secret bound via external.credentialsRef (the one external key a managed datasource may carry, spec #8153). The code is byte-identical in all 8 locale files. The surrounding sentence now says connection details come from env and the password lives in the secret store, so it stays true, and one sentence after the code explains load-time validation. updated set to 2026-10-29T10:00:00+08:00 in every locale, date untouched, zh-Hant regenerated. All three PM mechanism assumptions were confirmed on the published @objectstack/spec 18.2.0 (npm latest), with the code read on objectstack origin/main 4a00b2b7. defineDatasource was chosen because its own doc comment in 18.2.0 calls it preferred over a bare Datasource literal, and the objectstack external-datasources guide uses it.",
    "tests": "Probe against npm @objectstack/spec@18.2.0 (cli dist-tags latest=18.2.0). OLD sample: REFUSED [config] Unrecognized key connection; flattened: [config,password] not accepted inline + [config] user to username. NEW sample (code block extracted from index.mdx, run under node with env set): PARSES {host:crm-db.internal,database:crm,username:crm_readonly} credentialsRef sys_secret:sec_123 schemaMode managed, exit 0. Control: new + config.password REFUSED. Env unset: REFUSED [config,database] needs a connection target (loud, not silent defaults). tsc --strict 5.9.3 nodenext on the sample against 18.2.0 types: exit 0. sha256 of the ts block identical across all 8 files. No ablation: content-only diff, no tested behaviour changed.",
    "gates": "Head 12a9f3a, base main 6d95393; each exit captured to a file before reading output. pnpm install --frozen-lockfile: 0. pnpm content:lint: 0 (content lint passed, 383 files; no warning on this post). pnpm check (os-verify-lock): VERDICT command-exit 0, 0 errors / 0 warnings / 0 hints. pnpm build (os-verify-lock): VERDICT command-exit 0, 948 pages, content lint --dist links/CJK/quotes passed. pnpm seo:smoke: 0 (938 HTML pages checked). Browser: Playwright Chromium on built dist, en + zh-Hans + ja at 1440x750 and 390x844, screenshots viewed: renders, Updated Oct 29 2026, dateModified and article:modified_time 2026-10-29T02:00:00.000Z, no page-level horizontal overflow (code block scrolls inside pre at 390, same as before). dispatch-gates.mjs not used: www is a sister repo, list hand-derived from package.json + workflows. PR check-runs at report time: Build and deploy in_progress.",
    "line_budget": "8 files, +105 / -94 (content/blog/extend-existing-systems-with-ai/index*.mdx only)",
    "files_changed": [
    "content/blog/extend-existing-systems-with-ai/index.mdx",
    "content/blog/extend-existing-systems-with-ai/index.de.mdx",
    "content/blog/extend-existing-systems-with-ai/index.es.mdx",
    "content/blog/extend-existing-systems-with-ai/index.fr.mdx",
    "content/blog/extend-existing-systems-with-ai/index.ja.mdx",
    "content/blog/extend-existing-systems-with-ai/index.ko.mdx",
    "content/blog/extend-existing-systems-with-ai/index.zh-Hans.mdx",
    "content/blog/extend-existing-systems-with-ai/index.zh-Hant.mdx"
    ],
    "deviations": [
    "Commit trailer is model-free (Co-Authored-By: Claude noreply@anthropic.com + Claude-Session): the dispatch spelled a model-named Co-Authored-By and also said no model identifier in commits; the role file's model-free trailer rule decided it.",
    "Browser check ran the two viewports the dispatch named (1440x750, 390x844), not all six AGENTS.md lists.",
    "No PR labels: none named by the dispatch; www has no skip-changeset gate.",
    "On the issue comment the os-dev-report marker is line 2, after the seat signature line: post-stamped.mjs refuses a body whose first non-empty line is not the signature (measured, dry-run exit 2)."
    ],
    "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "0",
    "api_writes": "2: POST /repos/objectstack-ai/objectstack/dispatches (relay pr_create to www.objectos.ai#308, read-back 6938/6938 bytes identical, draft, no assignees); this os-dev-report comment via post-stamped.mjs. git push (2: empty-branch probe + 12a9f3a) is not a REST write.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: defineDatasource on @objectstack/spec 18.2.0, measured: a top-level password is refused with guidance 'Interpolate it from the environment inside config', but every driver contract refuses config.password (typed never), so following it leads to a second refusal · evidence: packages/spec/src/data/datasource.zod.ts DatasourceSchema guidance.password · dedupe words: datasource password guidance, interpolate from environment, config.password never, stale prescription",
    "class: a · reach: defineDatasource on 18.2.0, measured: external.password guidance gives the example credentialsRef 'secret:warehouse/password', which content/docs/data-modeling/external-datasources.mdx says will not dereference (only the opaque sys_secret:ID form resolves) · evidence: ExternalDatasourceSettingsSchema guidance.password · dedupe words: credentialsRef example, secret:warehouse/password, sys_secret handle, guidance example",
    "class: a · reach: published doc content/docs/data-modeling/drivers.mdx read-only section says 'external.credentialsRef is a federation key — the parse rejects an external block on a managed datasource — so a managed connection carries its credentials in config'; measured on 18.2.0, managed + external.credentialsRef parses, and its sample config: { url: process.env.ANALYTICS_RO_URL } is refused when the URL carries user:password · evidence: datasource.zod.ts #8153 managed allowance; probe output · dedupe words: drivers.mdx read-only, managed credentialsRef, credentials in config, url userinfo refused",
    "carrier: none · the three above are one family (stale datasource credential prescriptions in spec guidance + docs); file as one card in objectstack, not three"
    ]
    }

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions