Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 14 additions & 26 deletions .ai/typed-data-access.adf
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,35 @@ 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
- Business concepts (tenant, user, subscription, quota, credit, etc.) are defined in a canonical data registry — the single source of truth for ownership, sensitivity, and access shape across services
- Each concept declares: owner service, storage table, sensitivity tier, definition, aliases, and the accessor (RPC method, tool, or API) non-owning services must use
- Consumer services derive their known-concepts and alias maps from the registry at build time (a compiled-const snapshot pattern, not a runtime fetch)
- A disambiguation step halts on undefined concepts rather than guessing shape, ownership, or sensitivity
- This module ties together three generic mechanisms: registry → access 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)
- service_internal — readable/writable only by the owning service, raw storage access is fine within the owner (e.g., conversation, memory, log)
- cross_service_rpc — accessible via a declared RPC method or service binding, never raw storage access from a non-owning service (e.g., tenant, quota, generation)
- pii_scoped — accessible only via the owning service + an audit-log entry required at the call site (e.g., user)
- billing_critical — writable only by the owning service plus its designated payment-webhook handler; never leaves the owning service boundary even via RPC (e.g., subscription, invoice)
- 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
- Non-owning services reading or writing `cross_service_rpc` concepts MUST use the declared accessor — raw storage access to another service's table is a 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
- When promoting a concept to a higher sensitivity tier (e.g., service_internal → cross_service_rpc), all existing consumers of raw storage access to that concept must migrate to the declared accessor 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
- Use `charter surface --format json` to discover what tables/resources 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
- The disambiguation step is load-bearing for autonomous agents — they 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)
2 changes: 1 addition & 1 deletion packages/adf/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stackbilt/adf",
"sideEffects": false,
"version": "1.7.0",
"version": "1.8.0",
"description": "ADF (Attention-Directed Format) — AST-backed context format for AI agents",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/adf/src/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { parseManifest, resolveModules, buildTriggerReport } from './manifest';
import { mergeDocuments, estimateTokens } from './merger';

// Re-export for backward compatibility
export { parseManifest, resolveModules } from './manifest';
export { parseManifest, resolveModules, buildTriggerReport, formatTriggerEntry } from './manifest';

// ============================================================================
// Bundle Orchestration
Expand Down
2 changes: 1 addition & 1 deletion packages/adf/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { parseAdf } from './parser';
export { formatAdf } from './formatter';
export { applyPatches } from './patcher';
export { parseManifest, resolveModules, bundleModules } from './bundler';
export { parseManifest, resolveModules, buildTriggerReport, formatTriggerEntry, bundleModules } from './bundler';
export { validateConstraints, computeWeightSummary } from './validator';
export { evaluateLocBudgets, resolveBudgetStatus, matchPath } from './loc-budget';
export { evaluateEvidence } from './evidence';
Expand Down
14 changes: 14 additions & 0 deletions packages/adf/src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,20 @@ function parseTriggerEntry(entry: string): ManifestModule {
return mod;
}

/**
* Format a ManifestModule back into an ON_DEMAND bullet string — the
* inverse of parseTriggerEntry. Single source of truth for the bullet
* format so writers (e.g. `adf suggest --emit-ops`) can't drift from what
* parseTriggerEntry actually accepts.
*/
export function formatTriggerEntry(mod: ManifestModule): string {
const budgetSuffix = mod.tokenBudget !== undefined ? ` [budget: ${mod.tokenBudget}]` : '';
if (mod.triggers.length === 0) {
return `${mod.path}${budgetSuffix}`;
}
return `${mod.path} (Triggers on: ${mod.triggers.join(', ')})${budgetSuffix}`;
}

// ============================================================================
// Module Resolution
// ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stackbilt/cli",
"sideEffects": false,
"version": "1.7.0",
"version": "1.8.0",
"description": "Charter CLI — repo-level governance toolkit",
"bin": {
"charter": "./dist/bin.js"
Expand Down
Loading