From dbe27b311f0bd3531080fa9164463c4b00741a1b Mon Sep 17 00:00:00 2001 From: Kurt Overmier Date: Thu, 9 Apr 2026 14:08:26 -0500 Subject: [PATCH 1/2] feat(adf): typed-data-access policy module + named-scaffold registry (#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session 1 of charter#69 — typed data access + ontology enforcement policy. Ships the policy module and the infrastructure for consumer repos to adopt it via `charter adf create typed-data-access`. ## What this lands - **`.ai/typed-data-access.adf`** — dogfood module for charter itself. Documents the canonical data registry, 6 sensitivity tiers (public, service_internal, cross_service_rpc, pii_scoped, billing_critical, secrets), the disambiguation protocol, and the load-bearing access constraints (e.g., never leak billing_critical over RPC, HALT on undefined concepts instead of guessing). - **`.ai/manifest.adf`** — new ON_DEMAND entry so charter's own dev agent loads typed-data-access whenever work touches tenant/user/ subscription/quota/sensitivity/DATA_AUTHORITY/etc. keywords. - **`TYPED_DATA_ACCESS_SCAFFOLD` export in adf.ts** — the scaffold template as a reusable const, so consumer repos can adopt the policy with a single command. - **`NAMED_MODULE_SCAFFOLDS` registry + `NAMED_MODULE_DEFAULT_TRIGGERS`** — extensible map that `buildModuleScaffold` and `adfCreate` consult before falling back to the generic empty scaffold. First entry is typed-data-access; follow-up sessions can add more canonical modules (governance policies, compliance checks, etc.) behind the same mechanism. - **`adfCreate` change** — when the module name matches a named scaffold and no explicit `--triggers` is provided, the default trigger set is auto-applied so `charter adf create typed-data-access` produces a fully-wired ON_DEMAND entry with 16 business-concept keywords. ## What this does NOT land (Session 2+) - Charter governance check that flags references to unregistered terms (requires classify/validate integration) - `charter doctor` check for unregistered concepts - Codebeast DATA_AUTHORITY wiring - AEGIS disambiguation firewall integration with the canonical registry path (already consumes a build-time snapshot — see aegis web/src/lib/data-registry.ts — but doesn't hot-reload from charter) - Auto-scaffold via `charter bootstrap` (opt-in only via `charter adf create` for now — not every repo needs ontology enforcement) ## Validation - 9 new unit tests covering named scaffold registry, default triggers, sensitivity tier presence, registry path reference, and the load-bearing disambiguation constraint - 354/354 tests passing (+9 from 345) - End-to-end verification in a clean temp repo: `charter adf create typed-data-access` writes the rich scaffold AND registers it in ON_DEMAND with the 16 default triggers, no explicit flags needed - Full typecheck + build clean ## Known issue (pre-existing, not fixed here) Empty `📂 ON_DEMAND:` sections in a manifest cause `charter adf create` to fail with "ON_DEMAND must be a list section in manifest.adf" because the parser sees an empty key as a non-list. Workaround: manifests need at least one ON_DEMAND entry before `adf create` can append. Worth filing as a separate issue since it affects any `adf create` usage. ## References - Closes part of Stackbilt-dev/charter#69 (Session 1 of 4) - Registry source of truth: Stackbilt-dev/stackbilt_llc/policies/data-registry.yaml - Downstream: aegis web/src/lib/data-registry.ts (compiled consumer), aegis web/src/lib/disambiguation.ts (runtime firewall) - Related: codebeast#9 (DATA_AUTHORITY), aegis#344 (disambiguation) --- .ai/manifest.adf | 1 + .ai/typed-data-access.adf | 49 ++++++++ .../cli/src/__tests__/named-scaffolds.test.ts | 68 +++++++++++ packages/cli/src/commands/adf.ts | 113 +++++++++++++++++- 4 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 .ai/typed-data-access.adf create mode 100644 packages/cli/src/__tests__/named-scaffolds.test.ts diff --git a/.ai/manifest.adf b/.ai/manifest.adf index 84d9a97..67e0010 100644 --- a/.ai/manifest.adf +++ b/.ai/manifest.adf @@ -16,6 +16,7 @@ ADF: 0.1 - governance.adf (Triggers on: validate, drift, audit, trailer, risk, governance) - classifier.adf (Triggers on: classifier, routing, auto-route, adf add, classify, rule placement) - analysis.adf (Triggers on: blast, surface, dependency graph, blast radius, route extraction, schema extraction) + - typed-data-access.adf (Triggers on: tenant, user, subscription, quota, credit, mrr, pii, sensitivity, data registry, ontology, disambiguation, DATA_AUTHORITY, raw D1, service boundary, auth_scoped, billing_critical) 💰 BUDGET: MAX_TOKENS: 4000 diff --git a/.ai/typed-data-access.adf b/.ai/typed-data-access.adf new file mode 100644 index 0000000..e610144 --- /dev/null +++ b/.ai/typed-data-access.adf @@ -0,0 +1,49 @@ +ADF: 0.1 + +🎯 TASK: Typed data access and ontology enforcement policy + +📋 CONTEXT: + - Business concepts (tenant, user, subscription, quota, credit, mrr, etc.) are defined in a canonical data registry — the single source of truth for ownership, sensitivity, and access shape across the ecosystem + - Reference registry location: Stackbilt-dev/stackbilt_llc/policies/data-registry.yaml (22+ concepts, 6 sensitivity tiers) + - Each concept declares: owner service, D1 table, sensitivity tier, definition, aliases, rpc_method, mcp_tool + - Consumer services derive their KNOWN_CONCEPTS and alias maps from the registry at build time (see aegis-web/src/lib/data-registry.ts pattern — compiled-const snapshot) + - AEGIS disambiguation firewall halts on undefined concepts rather than guessing + - CodeBeast DATA_AUTHORITY sensitivity class escalates raw D1 access to owned tables + - This charter policy module ties the three mechanisms together: registry → enforcement → disambiguation + +🔐 SENSITIVITY TIERS [load-bearing]: + - public — readable from any service, no auth required (e.g., blog_post) + - service_internal — readable/writable only by the owning service, raw D1 access is fine within the owner (e.g., conversation, memory, llm_trace) + - cross_service_rpc — accessible via declared RPC method or Service Binding, never raw D1 from a non-owning service (e.g., tenant, quota, generation) + - pii_scoped — accessible only via owning service + audit_log entry required at the call site (e.g., user) + - billing_critical — writable only by the owning service plus the Stripe webhook handler; never leaves the owning service boundary even via RPC (e.g., subscription, mrr) + - secrets — never leaves the owning service boundary under any circumstance (e.g., api_key) + +⚠ CONSTRAINTS [load-bearing]: + - New code referencing a business concept MUST check the canonical registry first; terms not in the registry or its aliases MUST be added before the code lands + - Non-owning services reading or writing `cross_service_rpc` concepts MUST use the declared rpc_method or mcp_tool — raw D1 binding access to another service's table is a DATA_AUTHORITY violation + - `pii_scoped` access requires an audit_log entry at the call site — no silent reads + - `billing_critical` and `secrets` tiers NEVER cross the owning service boundary, even via RPC — treat as in-process only + - When encountering an undefined data concept in requirements, tasks, or user prompts, HALT and ask for clarification rather than guessing shape, ownership, or sensitivity + - Aliases (e.g., "credits" for "quota") are semantically equivalent; prefer the canonical form in new code, accept aliases in user-facing copy + - Registry updates MUST come before consumer code updates — the source of truth leads, consumers follow + - When promoting a concept to a higher sensitivity tier (e.g., service_internal → cross_service_rpc), all existing consumers of raw D1 access to that concept must migrate to RPC in the same change set + +📖 ADVISORY: + - Check the registry before reaching for a new type definition: the concept may already exist with a canonical shape + - Use `charter surface --format json` to discover what D1 tables a service currently exposes — cross-reference against registry ownership + - Unregistered-concept warnings from `charter validate` are early signals that ontology drift is beginning; address them immediately + - The disambiguation protocol is load-bearing for autonomous agents (AEGIS, cc-taskrunner, etc.) — these systems cannot safely guess business term semantics, and ambiguity compounds across sessions + +📊 METRICS: + REGISTRY_PATH: stackbilt_llc/policies/data-registry.yaml + REGISTRY_REPO: Stackbilt-dev/stackbilt_llc + SENSITIVITY_TIERS: 6 + DOCUMENTED_CONCEPTS: 22 + CONSUMER_PATTERN: web/src/lib/data-registry.ts (compile-from-yaml snapshot) + +🔗 REFERENCES: + - Stackbilt-dev/charter#69 — typed data access policy umbrella issue + - codebeast#9 — DATA_AUTHORITY sensitivity class (enforcement side) + - Stackbilt-dev/aegis#344 — disambiguation firewall (runtime halt mechanism) + - Stackbilt-dev/aegis#334 — adversarial reasoning (complementary quality layer) diff --git a/packages/cli/src/__tests__/named-scaffolds.test.ts b/packages/cli/src/__tests__/named-scaffolds.test.ts new file mode 100644 index 0000000..b7a22b6 --- /dev/null +++ b/packages/cli/src/__tests__/named-scaffolds.test.ts @@ -0,0 +1,68 @@ +import { describe, it, expect } from 'vitest'; +import { + NAMED_MODULE_SCAFFOLDS, + NAMED_MODULE_DEFAULT_TRIGGERS, + TYPED_DATA_ACCESS_SCAFFOLD, +} from '../commands/adf'; + +describe('NAMED_MODULE_SCAFFOLDS registry', () => { + it('contains typed-data-access scaffold entry', () => { + expect(NAMED_MODULE_SCAFFOLDS['typed-data-access']).toBeDefined(); + expect(NAMED_MODULE_SCAFFOLDS['typed-data-access']).toBe(TYPED_DATA_ACCESS_SCAFFOLD); + }); + + it('typed-data-access scaffold is valid ADF 0.1', () => { + const scaffold = NAMED_MODULE_SCAFFOLDS['typed-data-access']; + expect(scaffold).toMatch(/^ADF: 0\.1/); + }); + + it('typed-data-access scaffold declares the six sensitivity tiers', () => { + const scaffold = NAMED_MODULE_SCAFFOLDS['typed-data-access']; + expect(scaffold).toContain('public'); + expect(scaffold).toContain('service_internal'); + expect(scaffold).toContain('cross_service_rpc'); + expect(scaffold).toContain('pii_scoped'); + expect(scaffold).toContain('billing_critical'); + expect(scaffold).toContain('secrets'); + }); + + it('typed-data-access scaffold references the canonical registry path', () => { + const scaffold = NAMED_MODULE_SCAFFOLDS['typed-data-access']; + expect(scaffold).toContain('stackbilt_llc/policies/data-registry.yaml'); + }); + + it('typed-data-access scaffold includes load-bearing disambiguation constraint', () => { + const scaffold = NAMED_MODULE_SCAFFOLDS['typed-data-access']; + expect(scaffold).toMatch(/CONSTRAINTS \[load-bearing\]/); + expect(scaffold).toContain('HALT and ask'); + }); +}); + +describe('NAMED_MODULE_DEFAULT_TRIGGERS registry', () => { + it('contains typed-data-access trigger keywords', () => { + expect(NAMED_MODULE_DEFAULT_TRIGGERS['typed-data-access']).toBeDefined(); + expect(Array.isArray(NAMED_MODULE_DEFAULT_TRIGGERS['typed-data-access'])).toBe(true); + }); + + it('typed-data-access triggers include canonical business concept names', () => { + const triggers = NAMED_MODULE_DEFAULT_TRIGGERS['typed-data-access']; + expect(triggers).toContain('tenant'); + expect(triggers).toContain('user'); + expect(triggers).toContain('subscription'); + expect(triggers).toContain('quota'); + }); + + it('typed-data-access triggers include sensitivity and policy keywords', () => { + const triggers = NAMED_MODULE_DEFAULT_TRIGGERS['typed-data-access']; + expect(triggers).toContain('sensitivity'); + expect(triggers).toContain('DATA_AUTHORITY'); + expect(triggers).toContain('disambiguation'); + }); + + it('every named scaffold has default triggers registered', () => { + for (const name of Object.keys(NAMED_MODULE_SCAFFOLDS)) { + expect(NAMED_MODULE_DEFAULT_TRIGGERS[name]).toBeDefined(); + expect(NAMED_MODULE_DEFAULT_TRIGGERS[name].length).toBeGreaterThan(0); + } + }); +}); diff --git a/packages/cli/src/commands/adf.ts b/packages/cli/src/commands/adf.ts index 3c014b7..5872b23 100644 --- a/packages/cli/src/commands/adf.ts +++ b/packages/cli/src/commands/adf.ts @@ -114,6 +114,104 @@ export const CONTENT_SCAFFOLD = `ADF: 0.1 - Include alt text on all images `; +/** + * Typed data access and ontology enforcement policy (charter#69). + * + * This scaffold codifies the cross-repo policy for how services reference + * business concepts (tenant, user, subscription, quota, etc.) — derived from + * the canonical data registry at Stackbilt-dev/stackbilt_llc/policies/data-registry.yaml. + * + * Six sensitivity tiers, disambiguation protocol, and RPC boundary rules. + * Designed to be loaded on-demand when code touches data access or ontology + * keywords, and consumed by charter validate / codebeast DATA_AUTHORITY / + * AEGIS disambiguation firewall. + */ +export const TYPED_DATA_ACCESS_SCAFFOLD = `ADF: 0.1 + +\u{1F3AF} TASK: Typed data access and ontology enforcement policy + +\u{1F4CB} CONTEXT: + - Business concepts (tenant, user, subscription, quota, credit, mrr, etc.) are defined in a canonical data registry — the single source of truth for ownership, sensitivity, and access shape across the ecosystem + - Reference registry location: Stackbilt-dev/stackbilt_llc/policies/data-registry.yaml (22+ concepts, 6 sensitivity tiers) + - Each concept declares: owner service, D1 table, sensitivity tier, definition, aliases, rpc_method, mcp_tool + - Consumer services derive their KNOWN_CONCEPTS and alias maps from the registry at build time (compiled-const snapshot) + - Disambiguation protocol halts on undefined concepts rather than guessing + - CodeBeast DATA_AUTHORITY sensitivity class escalates raw D1 access to owned tables + +\u{1F510} SENSITIVITY TIERS [load-bearing]: + - public \u2014 readable from any service, no auth required (e.g., blog_post) + - service_internal \u2014 readable/writable only by the owning service, raw D1 access is fine within the owner + - cross_service_rpc \u2014 accessible via declared rpc_method or Service Binding, never raw D1 from a non-owning service + - pii_scoped \u2014 accessible only via owning service + audit_log entry required at the call site + - billing_critical \u2014 writable only by the owning service plus the Stripe webhook handler; never leaves the owning service boundary even via RPC + - secrets \u2014 never leaves the owning service boundary under any circumstance + +\u26A0\uFE0F CONSTRAINTS [load-bearing]: + - New code referencing a business concept MUST check the canonical registry first; terms not in the registry or its aliases MUST be added before the code lands + - Non-owning services reading or writing cross_service_rpc concepts MUST use the declared rpc_method or mcp_tool \u2014 raw D1 access to another service's table is a DATA_AUTHORITY violation + - pii_scoped access requires an audit_log entry at the call site \u2014 no silent reads + - billing_critical and secrets tiers NEVER cross the owning service boundary, even via RPC + - When encountering an undefined data concept in requirements, tasks, or user prompts, HALT and ask for clarification rather than guessing shape, ownership, or sensitivity + - Registry updates MUST come before consumer code updates \u2014 the source of truth leads, consumers follow + - When promoting a concept to a higher sensitivity tier, all existing consumers of raw D1 access must migrate to RPC in the same change set + +\u{1F4D6} ADVISORY: + - Check the registry before reaching for a new type definition \u2014 the concept may already exist with a canonical shape + - Use charter surface --format json to discover what D1 tables a service currently exposes; cross-reference against registry ownership + - Aliases (e.g., "credits" for "quota") are semantically equivalent; prefer the canonical form in new code, accept aliases in user-facing copy + - The disambiguation protocol is load-bearing for autonomous agents \u2014 these systems cannot safely guess business term semantics + +\u{1F4CA} METRICS: + REGISTRY_PATH: stackbilt_llc/policies/data-registry.yaml + REGISTRY_REPO: Stackbilt-dev/stackbilt_llc + SENSITIVITY_TIERS: 6 + DOCUMENTED_CONCEPTS: 22 + +\u{1F517} REFERENCES: + - Stackbilt-dev/charter#69 \u2014 typed data access policy umbrella issue + - codebeast#9 \u2014 DATA_AUTHORITY sensitivity class (enforcement side) + - Stackbilt-dev/aegis#344 \u2014 disambiguation firewall (runtime halt mechanism) +`; + +/** + * Registry of rich named-module scaffolds. When `charter adf create ` + * matches a name in this map, the corresponding scaffold is written instead + * of the generic empty placeholder from buildModuleScaffold's fallback. + * + * Modules added here should also be referenced from this file's documentation + * and — if they have canonical trigger keywords — the adfCreate command will + * use NAMED_MODULE_DEFAULT_TRIGGERS when no --triggers flag is provided. + */ +export const NAMED_MODULE_SCAFFOLDS: Record = { + 'typed-data-access': TYPED_DATA_ACCESS_SCAFFOLD, +}; + +/** + * Default manifest trigger keywords for named modules. Used when + * `charter adf create ` matches a known module and no explicit + * --triggers flag is provided. + */ +export const NAMED_MODULE_DEFAULT_TRIGGERS: Record = { + 'typed-data-access': [ + 'tenant', + 'user', + 'subscription', + 'quota', + 'credit', + 'mrr', + 'pii', + 'sensitivity', + 'data registry', + 'ontology', + 'disambiguation', + 'DATA_AUTHORITY', + 'raw D1', + 'service boundary', + 'auth_scoped', + 'billing_critical', + ], +}; + export const MANIFEST_FRONTEND_SCAFFOLD = `ADF: 0.1 \u{1F3AF} ROLE: Repo context router @@ -602,7 +700,12 @@ function adfCreate(options: CLIOptions, args: string[]): number { const manifestDoc = parseAdf(fs.readFileSync(manifestPath, 'utf-8')); const sectionKey = load === 'default' ? 'DEFAULT_LOAD' : 'ON_DEMAND'; - const triggers = parseTriggers(getFlag(args, '--triggers')); + const explicitTriggers = parseTriggers(getFlag(args, '--triggers')); + // Named-module modules can ship default triggers; explicit --triggers wins. + const moduleName = path.basename(moduleRelPath, '.adf'); + const triggers = explicitTriggers.length > 0 + ? explicitTriggers + : (NAMED_MODULE_DEFAULT_TRIGGERS[moduleName] ?? []); const manifestEntry = load === 'on-demand' && triggers.length > 0 ? `${moduleRelPath} (Triggers on: ${triggers.join(', ')})` : moduleRelPath; @@ -762,6 +865,14 @@ function parseModulePathFromEntry(entry: string): string { function buildModuleScaffold(modulePath: string): string { const name = path.basename(modulePath, '.adf'); + + // Named-module registry: rich scaffolds for canonical policy modules. + // Falls back to the generic placeholder below for user-defined modules. + const named = NAMED_MODULE_SCAFFOLDS[name]; + if (named) { + return named; + } + return `ADF: 0.1 \u{1F3AF} TASK: ${name} module From 2615a7a66cb0c5435b71a498800a49ab0a9c7e3d Mon Sep 17 00:00:00 2001 From: Kurt Overmier Date: Thu, 9 Apr 2026 14:13:53 -0500 Subject: [PATCH 2/2] refactor(adf): extract named-scaffolds to own file for LOC ceiling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adf_commands_loc: 884 / 800 → 796 / 800. The TYPED_DATA_ACCESS_SCAFFOLD + NAMED_MODULE_SCAFFOLDS + NAMED_MODULE_DEFAULT_TRIGGERS blew the 800-line ceiling on packages/cli/src/commands/adf.ts when folded inline. Moved them to a dedicated adf-named-scaffolds.ts alongside the other adf-*.ts module files. adf.ts imports the maps for use by buildModuleScaffold and adfCreate, and re-exports them for backward compat with programmatic consumers + tests. This also sets up a clean pattern for adding more named scaffolds in future sessions (Session 2+ can add scaffolds for governance policies, compliance modules, etc. to adf-named-scaffolds.ts without touching adf.ts again). --- .../cli/src/__tests__/named-scaffolds.test.ts | 2 +- .../cli/src/commands/adf-named-scaffolds.ts | 113 ++++++++++++++++++ packages/cli/src/commands/adf.ts | 109 ++--------------- 3 files changed, 125 insertions(+), 99 deletions(-) create mode 100644 packages/cli/src/commands/adf-named-scaffolds.ts diff --git a/packages/cli/src/__tests__/named-scaffolds.test.ts b/packages/cli/src/__tests__/named-scaffolds.test.ts index b7a22b6..db1dea4 100644 --- a/packages/cli/src/__tests__/named-scaffolds.test.ts +++ b/packages/cli/src/__tests__/named-scaffolds.test.ts @@ -3,7 +3,7 @@ import { NAMED_MODULE_SCAFFOLDS, NAMED_MODULE_DEFAULT_TRIGGERS, TYPED_DATA_ACCESS_SCAFFOLD, -} from '../commands/adf'; +} from '../commands/adf-named-scaffolds'; describe('NAMED_MODULE_SCAFFOLDS registry', () => { it('contains typed-data-access scaffold entry', () => { diff --git a/packages/cli/src/commands/adf-named-scaffolds.ts b/packages/cli/src/commands/adf-named-scaffolds.ts new file mode 100644 index 0000000..8307eaf --- /dev/null +++ b/packages/cli/src/commands/adf-named-scaffolds.ts @@ -0,0 +1,113 @@ +/** + * Named-module scaffold registry. + * + * Rich scaffolds for canonical policy modules that consumer repos can adopt + * with `charter adf create `. The generic empty placeholder in + * buildModuleScaffold is the fallback; entries in NAMED_MODULE_SCAFFOLDS + * take precedence. + * + * Each named module also registers default manifest trigger keywords in + * NAMED_MODULE_DEFAULT_TRIGGERS. When `charter adf create ` is called + * without an explicit --triggers flag, these auto-populate the ON_DEMAND + * entry so the wiring is a one-command operation. + * + * Adding a new named module: + * 1. Add the scaffold content as an exported const + * 2. Register it in NAMED_MODULE_SCAFFOLDS + * 3. Register default triggers in NAMED_MODULE_DEFAULT_TRIGGERS + * 4. Add tests in __tests__/named-scaffolds.test.ts + */ + +/** + * Typed data access and ontology enforcement policy (Stackbilt-dev/charter#69). + * + * Codifies the cross-repo policy for how services reference business concepts + * (tenant, user, subscription, quota, etc.) — derived from the canonical data + * registry at Stackbilt-dev/stackbilt_llc/policies/data-registry.yaml. + * + * Declares six sensitivity tiers, the disambiguation protocol, and RPC + * boundary rules. Consumed by charter validate / codebeast DATA_AUTHORITY / + * AEGIS disambiguation firewall as the single source of truth for data + * access policy across the ecosystem. + */ +export const TYPED_DATA_ACCESS_SCAFFOLD = `ADF: 0.1 + +\u{1F3AF} TASK: Typed data access and ontology enforcement policy + +\u{1F4CB} CONTEXT: + - Business concepts (tenant, user, subscription, quota, credit, mrr, etc.) are defined in a canonical data registry — the single source of truth for ownership, sensitivity, and access shape across the ecosystem + - Reference registry location: Stackbilt-dev/stackbilt_llc/policies/data-registry.yaml (22+ concepts, 6 sensitivity tiers) + - Each concept declares: owner service, D1 table, sensitivity tier, definition, aliases, rpc_method, mcp_tool + - Consumer services derive their KNOWN_CONCEPTS and alias maps from the registry at build time (compiled-const snapshot) + - Disambiguation protocol halts on undefined concepts rather than guessing + - CodeBeast DATA_AUTHORITY sensitivity class escalates raw D1 access to owned tables + +\u{1F510} SENSITIVITY TIERS [load-bearing]: + - public \u2014 readable from any service, no auth required (e.g., blog_post) + - service_internal \u2014 readable/writable only by the owning service, raw D1 access is fine within the owner + - cross_service_rpc \u2014 accessible via declared rpc_method or Service Binding, never raw D1 from a non-owning service + - pii_scoped \u2014 accessible only via owning service + audit_log entry required at the call site + - billing_critical \u2014 writable only by the owning service plus the Stripe webhook handler; never leaves the owning service boundary even via RPC + - secrets \u2014 never leaves the owning service boundary under any circumstance + +\u26A0\uFE0F CONSTRAINTS [load-bearing]: + - New code referencing a business concept MUST check the canonical registry first; terms not in the registry or its aliases MUST be added before the code lands + - Non-owning services reading or writing cross_service_rpc concepts MUST use the declared rpc_method or mcp_tool \u2014 raw D1 access to another service's table is a DATA_AUTHORITY violation + - pii_scoped access requires an audit_log entry at the call site \u2014 no silent reads + - billing_critical and secrets tiers NEVER cross the owning service boundary, even via RPC + - When encountering an undefined data concept in requirements, tasks, or user prompts, HALT and ask for clarification rather than guessing shape, ownership, or sensitivity + - Registry updates MUST come before consumer code updates \u2014 the source of truth leads, consumers follow + - When promoting a concept to a higher sensitivity tier, all existing consumers of raw D1 access must migrate to RPC in the same change set + +\u{1F4D6} ADVISORY: + - Check the registry before reaching for a new type definition \u2014 the concept may already exist with a canonical shape + - Use charter surface --format json to discover what D1 tables a service currently exposes; cross-reference against registry ownership + - Aliases (e.g., "credits" for "quota") are semantically equivalent; prefer the canonical form in new code, accept aliases in user-facing copy + - The disambiguation protocol is load-bearing for autonomous agents \u2014 these systems cannot safely guess business term semantics + +\u{1F4CA} METRICS: + REGISTRY_PATH: stackbilt_llc/policies/data-registry.yaml + REGISTRY_REPO: Stackbilt-dev/stackbilt_llc + SENSITIVITY_TIERS: 6 + DOCUMENTED_CONCEPTS: 22 + +\u{1F517} REFERENCES: + - Stackbilt-dev/charter#69 \u2014 typed data access policy umbrella issue + - codebeast#9 \u2014 DATA_AUTHORITY sensitivity class (enforcement side) + - Stackbilt-dev/aegis#344 \u2014 disambiguation firewall (runtime halt mechanism) +`; + +/** + * Registry of rich named-module scaffolds. When `charter adf create ` + * matches a name in this map, the corresponding scaffold is written instead + * of the generic empty placeholder from buildModuleScaffold's fallback. + */ +export const NAMED_MODULE_SCAFFOLDS: Record = { + 'typed-data-access': TYPED_DATA_ACCESS_SCAFFOLD, +}; + +/** + * Default manifest trigger keywords for named modules. Used when + * `charter adf create ` matches a known module and no explicit + * --triggers flag is provided. + */ +export const NAMED_MODULE_DEFAULT_TRIGGERS: Record = { + 'typed-data-access': [ + 'tenant', + 'user', + 'subscription', + 'quota', + 'credit', + 'mrr', + 'pii', + 'sensitivity', + 'data registry', + 'ontology', + 'disambiguation', + 'DATA_AUTHORITY', + 'raw D1', + 'service boundary', + 'auth_scoped', + 'billing_critical', + ], +}; diff --git a/packages/cli/src/commands/adf.ts b/packages/cli/src/commands/adf.ts index 5872b23..d453275 100644 --- a/packages/cli/src/commands/adf.ts +++ b/packages/cli/src/commands/adf.ts @@ -24,6 +24,17 @@ import { adfMetricsCommand } from './adf-metrics'; import { adfTidyCommand } from './adf-tidy'; import { adfPopulateCommand } from './adf-populate'; import { adfContextCommand } from './adf-context'; +import { + NAMED_MODULE_SCAFFOLDS, + NAMED_MODULE_DEFAULT_TRIGGERS, +} from './adf-named-scaffolds'; + +// Re-export named-scaffold registry for programmatic consumers and tests. +export { + TYPED_DATA_ACCESS_SCAFFOLD, + NAMED_MODULE_SCAFFOLDS, + NAMED_MODULE_DEFAULT_TRIGGERS, +} from './adf-named-scaffolds'; // ============================================================================ // Scaffold Content @@ -114,104 +125,6 @@ export const CONTENT_SCAFFOLD = `ADF: 0.1 - Include alt text on all images `; -/** - * Typed data access and ontology enforcement policy (charter#69). - * - * This scaffold codifies the cross-repo policy for how services reference - * business concepts (tenant, user, subscription, quota, etc.) — derived from - * the canonical data registry at Stackbilt-dev/stackbilt_llc/policies/data-registry.yaml. - * - * Six sensitivity tiers, disambiguation protocol, and RPC boundary rules. - * Designed to be loaded on-demand when code touches data access or ontology - * keywords, and consumed by charter validate / codebeast DATA_AUTHORITY / - * AEGIS disambiguation firewall. - */ -export const TYPED_DATA_ACCESS_SCAFFOLD = `ADF: 0.1 - -\u{1F3AF} TASK: Typed data access and ontology enforcement policy - -\u{1F4CB} CONTEXT: - - Business concepts (tenant, user, subscription, quota, credit, mrr, etc.) are defined in a canonical data registry — the single source of truth for ownership, sensitivity, and access shape across the ecosystem - - Reference registry location: Stackbilt-dev/stackbilt_llc/policies/data-registry.yaml (22+ concepts, 6 sensitivity tiers) - - Each concept declares: owner service, D1 table, sensitivity tier, definition, aliases, rpc_method, mcp_tool - - Consumer services derive their KNOWN_CONCEPTS and alias maps from the registry at build time (compiled-const snapshot) - - Disambiguation protocol halts on undefined concepts rather than guessing - - CodeBeast DATA_AUTHORITY sensitivity class escalates raw D1 access to owned tables - -\u{1F510} SENSITIVITY TIERS [load-bearing]: - - public \u2014 readable from any service, no auth required (e.g., blog_post) - - service_internal \u2014 readable/writable only by the owning service, raw D1 access is fine within the owner - - cross_service_rpc \u2014 accessible via declared rpc_method or Service Binding, never raw D1 from a non-owning service - - pii_scoped \u2014 accessible only via owning service + audit_log entry required at the call site - - billing_critical \u2014 writable only by the owning service plus the Stripe webhook handler; never leaves the owning service boundary even via RPC - - secrets \u2014 never leaves the owning service boundary under any circumstance - -\u26A0\uFE0F CONSTRAINTS [load-bearing]: - - New code referencing a business concept MUST check the canonical registry first; terms not in the registry or its aliases MUST be added before the code lands - - Non-owning services reading or writing cross_service_rpc concepts MUST use the declared rpc_method or mcp_tool \u2014 raw D1 access to another service's table is a DATA_AUTHORITY violation - - pii_scoped access requires an audit_log entry at the call site \u2014 no silent reads - - billing_critical and secrets tiers NEVER cross the owning service boundary, even via RPC - - When encountering an undefined data concept in requirements, tasks, or user prompts, HALT and ask for clarification rather than guessing shape, ownership, or sensitivity - - Registry updates MUST come before consumer code updates \u2014 the source of truth leads, consumers follow - - When promoting a concept to a higher sensitivity tier, all existing consumers of raw D1 access must migrate to RPC in the same change set - -\u{1F4D6} ADVISORY: - - Check the registry before reaching for a new type definition \u2014 the concept may already exist with a canonical shape - - Use charter surface --format json to discover what D1 tables a service currently exposes; cross-reference against registry ownership - - Aliases (e.g., "credits" for "quota") are semantically equivalent; prefer the canonical form in new code, accept aliases in user-facing copy - - The disambiguation protocol is load-bearing for autonomous agents \u2014 these systems cannot safely guess business term semantics - -\u{1F4CA} METRICS: - REGISTRY_PATH: stackbilt_llc/policies/data-registry.yaml - REGISTRY_REPO: Stackbilt-dev/stackbilt_llc - SENSITIVITY_TIERS: 6 - DOCUMENTED_CONCEPTS: 22 - -\u{1F517} REFERENCES: - - Stackbilt-dev/charter#69 \u2014 typed data access policy umbrella issue - - codebeast#9 \u2014 DATA_AUTHORITY sensitivity class (enforcement side) - - Stackbilt-dev/aegis#344 \u2014 disambiguation firewall (runtime halt mechanism) -`; - -/** - * Registry of rich named-module scaffolds. When `charter adf create ` - * matches a name in this map, the corresponding scaffold is written instead - * of the generic empty placeholder from buildModuleScaffold's fallback. - * - * Modules added here should also be referenced from this file's documentation - * and — if they have canonical trigger keywords — the adfCreate command will - * use NAMED_MODULE_DEFAULT_TRIGGERS when no --triggers flag is provided. - */ -export const NAMED_MODULE_SCAFFOLDS: Record = { - 'typed-data-access': TYPED_DATA_ACCESS_SCAFFOLD, -}; - -/** - * Default manifest trigger keywords for named modules. Used when - * `charter adf create ` matches a known module and no explicit - * --triggers flag is provided. - */ -export const NAMED_MODULE_DEFAULT_TRIGGERS: Record = { - 'typed-data-access': [ - 'tenant', - 'user', - 'subscription', - 'quota', - 'credit', - 'mrr', - 'pii', - 'sensitivity', - 'data registry', - 'ontology', - 'disambiguation', - 'DATA_AUTHORITY', - 'raw D1', - 'service boundary', - 'auth_scoped', - 'billing_critical', - ], -}; - export const MANIFEST_FRONTEND_SCAFFOLD = `ADF: 0.1 \u{1F3AF} ROLE: Repo context router