@vegastack/skills 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +9 -9
  3. package/package.json +1 -1
  4. package/skill/arch-guardian/SKILL.md +27 -39
  5. package/skill/arch-guardian/agents/openai.yaml +2 -2
  6. package/skill/arch-guardian/assets/adr-template.md +10 -25
  7. package/skill/arch-guardian/assets/answers-example.json +8 -18
  8. package/skill/arch-guardian/assets/architecture-profile.json +10 -22
  9. package/skill/arch-guardian/assets/architecture-profile.schema.json +13 -195
  10. package/skill/arch-guardian/references/advisory-report.md +65 -0
  11. package/skill/arch-guardian/references/architecture/ai-cost.md +2 -0
  12. package/skill/arch-guardian/references/architecture/ai-data-boundaries.md +2 -0
  13. package/skill/arch-guardian/references/architecture/ai-evals.md +2 -0
  14. package/skill/arch-guardian/references/architecture/connectors-sandbox.md +2 -2
  15. package/skill/arch-guardian/references/architecture/delivery-operations.md +5 -5
  16. package/skill/arch-guardian/references/architecture/durable-execution.md +3 -1
  17. package/skill/arch-guardian/references/architecture/flutter.md +1 -1
  18. package/skill/arch-guardian/references/architecture/foundation.md +17 -19
  19. package/skill/arch-guardian/references/architecture/hosting-reliability.md +1 -1
  20. package/skill/arch-guardian/references/architecture/identity-tenancy.md +7 -7
  21. package/skill/arch-guardian/references/architecture/model-lifecycle.md +5 -3
  22. package/skill/arch-guardian/references/architecture/models-observability.md +1 -1
  23. package/skill/arch-guardian/references/architecture/security-privacy.md +9 -5
  24. package/skill/arch-guardian/references/architecture/topology-monorepo.md +1 -1
  25. package/skill/arch-guardian/references/architecture/web.md +1 -1
  26. package/skill/arch-guardian/references/foundation-compatibility.json +1 -1
  27. package/skill/arch-guardian/references/profile-governance.md +25 -39
  28. package/skill/arch-guardian/references/rule-model.json +2 -2
  29. package/skill/arch-guardian/references/workflows.md +15 -12
  30. package/skill/arch-guardian/refresh/REFRESH.md +9 -2
  31. package/skill/arch-guardian/refresh/sources.json +26 -14
  32. package/skill/arch-guardian/scripts/lib.mjs +1 -10
  33. package/skill/arch-guardian/scripts/profile-tool.mjs +43 -49
  34. package/skill/arch-guardian/scripts/refresh-evidence.mjs +43 -5
  35. package/skill/arch-guardian/scripts/validate-profile.mjs +20 -196
  36. package/skill/arch-guardian/scripts/verify-corpus.mjs +1 -13
  37. package/skill/skill-maintainer/refresh/REFRESH.md +1 -1
  38. package/skill-integrity.json +34 -35
  39. package/skill/arch-guardian/references/control-catalog.json +0 -55
  40. package/skill/arch-guardian/scripts/architecture-check.mjs +0 -323
@@ -1,14 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { lstat, readFile, realpath } from 'node:fs/promises'
3
- import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { dirname, join, resolve } from 'node:path'
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url'
5
- import { listFiles, pathExists, readJsonYaml, resolveProfile } from './lib.mjs'
5
+ import { listFiles, readJsonYaml, resolveProfile } from './lib.mjs'
6
6
  import { validateJsonSchema } from './schema-validate.mjs'
7
7
 
8
8
  const skillRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
9
9
  const architectureRoot = join(skillRoot, 'references', 'architecture')
10
10
  const schemaPath = join(skillRoot, 'assets', 'architecture-profile.schema.json')
11
- const compatibilityPath = join(skillRoot, 'references', 'foundation-compatibility.json')
12
11
 
13
12
  export async function canonicalRuleIds() {
14
13
  const ids = new Set()
@@ -24,203 +23,28 @@ export async function canonicalRuleIds() {
24
23
  return ids
25
24
  }
26
25
 
27
- const enabled = (profile, name) => profile.capabilities?.[name]?.status === 'enabled'
28
- const control = (profile, capability, name) => profile.capabilities?.[capability]?.controls?.[name]
29
- const exactVersion = /^\d+\.\d+(?:\.\d+)?(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/
30
- const diag = (rule, controlId, path, message, exceptionEligible = true) => ({ rule, control: controlId, path, message, exceptionEligible })
31
-
32
- async function containedFile(projectRoot, targetPath) {
33
- if (isAbsolute(targetPath) || targetPath.split(/[\\/]/).includes('..')) throw new Error('path must be repository-relative without ..')
34
- const target = resolve(projectRoot, targetPath)
35
- let current = resolve(projectRoot)
36
- for (const part of relative(current, target).split(sep).filter(Boolean)) {
37
- current = resolve(current, part)
38
- if ((await lstat(current)).isSymbolicLink()) throw new Error('symlink component')
39
- }
40
- const resolvedRoot = await realpath(projectRoot)
41
- const resolvedTarget = await realpath(target)
42
- if (relative(resolvedRoot, resolvedTarget).startsWith('..') || !(await lstat(resolvedTarget)).isFile()) throw new Error('outside project or not a file')
43
- return resolvedTarget
44
- }
45
-
46
- function metadata(body, key) {
47
- return body.match(new RegExp(`^- ${key}:\\s*(.+)$`, 'mi'))?.[1]?.trim()
48
- }
49
-
50
- async function validateExceptions(profile, projectRoot, now, knownRules) {
51
- const states = []
52
- const seen = new Set()
53
- for (const exception of profile.exceptions ?? []) {
54
- const reasons = []
55
- if (seen.has(exception.id)) reasons.push(`duplicate exception id ${exception.id}`)
56
- seen.add(exception.id)
57
- if (!knownRules.has(exception.ruleId)) reasons.push(`unknown canonical rule ${exception.ruleId}`)
58
- for (const path of exception.paths ?? []) if (path.includes('*')) reasons.push('wildcard paths are forbidden')
59
- if (exception.review?.date) {
60
- const [year, month, day] = String(exception.review.date).split('-').map(Number)
61
- const parsed = new Date(Date.UTC(year, month - 1, day))
62
- const validCalendarDate = /^\d{4}-\d{2}-\d{2}$/.test(exception.review.date) && parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month - 1 && parsed.getUTCDate() === day
63
- const expiry = validCalendarDate ? Date.parse(`${exception.review.date}T23:59:59Z`) : Number.NaN
64
- if (!Number.isFinite(expiry)) reasons.push('review.date is not a real ISO calendar date')
65
- else if (expiry < now.getTime()) reasons.push(`exception expired on ${exception.review.date}`)
66
- }
67
- try {
68
- const adrPath = await containedFile(projectRoot, String(exception.adr ?? ''))
69
- const body = await readFile(adrPath, 'utf8')
70
- const requiredHeadings = ['## Decision and rationale', '## Risks and accepted deviation', '## Compensating controls', '## Verification', '## Rollback or migration', '## Review trigger']
71
- for (const heading of requiredHeadings) if (!body.includes(heading)) reasons.push(`ADR missing ${heading}`)
72
- const checks = {
73
- 'Status': 'accepted',
74
- 'Project-Owner': exception.projectOwner,
75
- 'Rule-ID': exception.ruleId,
76
- 'Exception-ID': exception.id,
77
- 'Foundation-Deviation-Acknowledged': 'true'
78
- }
79
- for (const [key, expected] of Object.entries(checks)) if (metadata(body, key) !== String(expected)) reasons.push(`ADR ${key} must equal ${expected}`)
80
- const adrControls = new Set((metadata(body, 'Control-IDs') ?? '').split(',').map(item => item.trim()).filter(Boolean))
81
- const adrPaths = new Set((metadata(body, 'Scope-Paths') ?? '').split(',').map(item => item.trim()).filter(Boolean))
82
- if (exception.controls) {
83
- if (exception.controls.some(item => !adrControls.has(item)) || adrControls.size !== exception.controls.length) reasons.push('ADR Control-IDs do not exactly match profile')
84
- } else if (metadata(body, 'Control-IDs') !== 'all') {
85
- // A rule-level exception (no controls list) covers every control under its rule; the ADR
86
- // must acknowledge that breadth explicitly.
87
- reasons.push('ADR Control-IDs must equal all for a rule-level exception without a controls list')
88
- }
89
- if ((exception.paths ?? []).some(item => !adrPaths.has(item)) || adrPaths.size !== (exception.paths ?? []).length) reasons.push('ADR Scope-Paths do not exactly match profile')
90
- if (exception.review?.date && metadata(body, 'Review-Date') !== exception.review.date) reasons.push('ADR Review-Date does not match profile')
91
- if (exception.review?.event && metadata(body, 'Review-Event') !== exception.review.event) reasons.push('ADR Review-Event does not match profile')
92
- } catch (error) { reasons.push(`ADR invalid or not found: ${error.message}`) }
93
- if (exception.status !== 'active') reasons.push(`exception status is ${exception.status}`)
94
- states.push({ exception, valid: reasons.length === 0, reasons })
95
- }
96
- return states
26
+ // Collect every REQUIRED-* placeholder still present so a draft cannot be mistaken for
27
+ // confirmed facts.
28
+ function unconfirmedValues(value, path = '') {
29
+ if (typeof value === 'string') return value.startsWith('REQUIRED-') ? [path || '(root)'] : []
30
+ if (Array.isArray(value)) return value.flatMap((item, index) => unconfirmedValues(item, `${path}[${index}]`))
31
+ if (value && typeof value === 'object') return Object.entries(value).flatMap(([key, item]) => unconfirmedValues(item, path ? `${path}.${key}` : key))
32
+ return []
97
33
  }
98
34
 
99
- function requiredFamilyVersions(profile, compatibility) {
100
- const baseline = compatibility.baselines?.[profile.foundation?.baseline]
101
- if (!baseline) return []
102
- const requirements = []
103
- const add = (capability, family) => {
104
- if (!enabled(profile, capability)) return
105
- for (const [key, expected] of Object.entries(baseline.families?.[family] ?? {})) requirements.push({ capability, key, expected })
106
- }
107
- add('webControlPlane', 'web')
108
- if (enabled(profile, 'webControlPlane') && profile.environments?.production?.hosting === 'cloudflare-opennext') add('webControlPlane', 'cloudflare-opennext')
109
- add('agents', 'agents')
110
- add('jobs', 'jobs')
111
- if (enabled(profile, 'webControlPlane') && profile.project?.access !== 'public') add('webControlPlane', 'identity')
112
- if (enabled(profile, 'sandbox') && control(profile, 'sandbox', 'provider') === 'cloudflare-sandbox') add('sandbox', 'sandbox-cloudflare')
113
- return requirements
114
- }
115
-
116
- function profileRelativeName(path, projectRoot) {
117
- const absolute = resolve(path)
118
- if (projectRoot) {
119
- const relativePath = relative(resolve(projectRoot), absolute).split(sep).join('/')
120
- if (!relativePath.startsWith('..')) return relativePath
121
- }
122
- return basename(dirname(absolute)) === '.vegastack' ? `.vegastack/${basename(absolute)}` : basename(absolute)
123
- }
124
-
125
- export async function validateProfile(path, options = {}) {
126
- const diagnostics = []
127
- const profileRel = profileRelativeName(path, options.projectRoot)
35
+ export async function validateProfile(path) {
128
36
  let profile
129
37
  try { profile = await readJsonYaml(path) } catch (error) {
130
- return { profile: null, errors: [error.message], diagnostics: [diag('FOUND-001', 'profile.parse', profileRel, error.message, false)], exceptions: [] }
38
+ return { profile: null, errors: [error.message] }
131
39
  }
132
- if (profile.schemaVersion === 2) {
133
- const message = 'schemaVersion 2 is obsolete; run profile-tool.mjs migrate-v2 <profile> for a deterministic read-only v3 draft, then confirm project facts'
134
- return { profile, errors: [message], diagnostics: [diag('FOUND-001', 'profile.schema-version', profileRel, message, false)], exceptions: [] }
40
+ if (profile.schemaVersion === 2 || profile.schemaVersion === 3) {
41
+ return { profile, errors: [`schemaVersion ${profile.schemaVersion} is obsolete; run profile-tool.mjs migrate <profile> for a deterministic read-only v4 draft, then confirm project facts (v2 needs a fresh v4 profile from answers)`] }
135
42
  }
43
+ const errors = []
136
44
  const schema = JSON.parse(await readFile(schemaPath, 'utf8'))
137
- for (const message of validateJsonSchema(schema, profile)) diagnostics.push(diag('FOUND-001', 'profile.schema', profileRel, message, false))
138
- const profileDirectory = dirname(resolve(path))
139
- const projectRoot = resolve(options.projectRoot ?? (basename(profileDirectory) === '.vegastack' ? dirname(profileDirectory) : process.cwd()))
140
- const compatibility = JSON.parse(await readFile(compatibilityPath, 'utf8'))
141
- const knownRules = await canonicalRuleIds()
142
- const now = options.now ? new Date(options.now) : new Date()
143
- const exceptionStates = await validateExceptions(profile, projectRoot, now, knownRules)
144
- for (const state of exceptionStates.filter(item => !item.valid)) diagnostics.push(diag('FOUND-002', 'exception.validity', profileRel, `${state.exception?.id ?? 'unknown'}: ${state.reasons.join('; ')}`, false))
145
-
146
- if (profile.profileStatus !== 'confirmed') diagnostics.push(diag('FOUND-001', 'profile.confirmed', profileRel, 'profileStatus must be confirmed before CI; the bundled profile is an intentionally incomplete draft', false))
147
- if (String(profile.project?.name ?? '').startsWith('REQUIRED-')) diagnostics.push(diag('FOUND-001', 'profile.project-name', profileRel, 'replace the REQUIRED project name with a confirmed fact', false))
148
- const baseline = compatibility.baselines?.[profile.foundation?.baseline]
149
- if (!baseline) diagnostics.push(diag('PKG-003', 'foundation.baseline', profileRel, `unknown foundation baseline ${profile.foundation?.baseline}`))
150
- else if (profile.foundation?.adoption !== baseline.state) diagnostics.push(diag('PKG-003', 'foundation.adoption', profileRel, `baseline ${profile.foundation.baseline} has state ${baseline.state}, not ${profile.foundation?.adoption}`))
151
- else if (baseline.state !== 'supported') diagnostics.push(diag('PKG-003', 'foundation.non-supported-adoption', profileRel, `${baseline.state} baseline adoption requires scoped project-owner ADR and qualification evidence`))
152
-
153
- for (const [name, capability] of Object.entries(profile.capabilities ?? {})) {
154
- if (capability?.status !== 'enabled') continue
155
- for (const [key, version] of Object.entries(capability.versions ?? {})) if (!exactVersion.test(String(version))) diagnostics.push(diag('PKG-003', `version.${name}.${key}`, profileRel, `${name}.versions.${key} must be an exact version`))
156
- if (capability.ownership === 'owned') for (const root of capability.sourceRoots ?? []) {
157
- const full = resolve(projectRoot, root)
158
- try {
159
- if (relative(projectRoot, full).startsWith('..')) throw new Error('outside project')
160
- if ((await lstat(full)).isSymbolicLink()) throw new Error('symlink')
161
- if (!(await lstat(full)).isDirectory()) throw new Error('not a directory')
162
- } catch (error) { diagnostics.push(diag('FOUND-004', `capability.${name}.source-root`, root, `owned enabled capability ${name} source root is unavailable: ${error.message}`)) }
163
- }
164
- }
165
- const separatedRuntimes = new Set(['webControlPlane', 'agents', 'jobs'])
166
- const ownedRoots = Object.entries(profile.capabilities ?? {}).flatMap(([name, cap]) => separatedRuntimes.has(name) && cap?.status === 'enabled' && cap?.ownership === 'owned' ? (cap.sourceRoots ?? []).map(root => [name, root]) : [])
167
- for (let left = 0; left < ownedRoots.length; left += 1) for (let right = left + 1; right < ownedRoots.length; right += 1) {
168
- const [leftOwner, leftRoot] = ownedRoots[left]
169
- const [rightOwner, rightRoot] = ownedRoots[right]
170
- if (leftOwner !== rightOwner && (leftRoot === rightRoot || leftRoot.startsWith(`${rightRoot}/`) || rightRoot.startsWith(`${leftRoot}/`))) diagnostics.push(diag('RUN-003', 'capability.source-root-overlap', profileRel, `owned source roots overlap between ${leftOwner} and ${rightOwner}: ${leftRoot} / ${rightRoot}`))
171
- }
172
-
173
- for (const item of requiredFamilyVersions(profile, compatibility)) {
174
- const actual = profile.capabilities?.[item.capability]?.versions?.[item.key]
175
- if (actual !== item.expected) diagnostics.push(diag('PKG-003', `version.${item.capability}.${item.key}`, profileRel, `${item.capability}.versions.${item.key} must equal supported baseline ${item.expected}; lag/advance requires a scoped project ADR`))
176
- }
177
-
178
- if (enabled(profile, 'flutter')) {
179
- if (!enabled(profile, 'webControlPlane')) diagnostics.push(diag('MOB-002', 'flutter.api-provider', profileRel, 'Flutter requires an enabled REST/OpenAPI provider capability'))
180
- if (control(profile, 'flutter', 'delegatedOAuthPkce') !== true) diagnostics.push(diag('MOB-001', 'flutter.oauth-pkce', profileRel, 'Flutter requires delegated OAuth/OIDC code with S256 PKCE'))
181
- if (control(profile, 'flutter', 'generatedRestClient') !== true || control(profile, 'webControlPlane', 'openapiGenerated') !== true) diagnostics.push(diag('MOB-002', 'flutter.generated-client', profileRel, 'Flutter requires deterministic generated REST/OpenAPI client consumption'))
182
- }
183
- if (enabled(profile, 'agents')) {
184
- if (control(profile, 'agents', 'workflowWorld') !== 'postgres' || control(profile, 'agents', 'agentRun') !== true) diagnostics.push(diag('DUR-001', 'agents.durable-owner', profileRel, 'Agents require qualified EVE/Postgres World and AgentRun'))
185
- if (profile.capabilities.agents.ownership === 'owned' && (!control(profile, 'agents', 'workflowDatabaseOwner') || !control(profile, 'agents', 'agentRunOwner'))) diagnostics.push(diag('DUR-001', 'agents.storage-ownership', profileRel, 'Owned agents must declare the Workflow PostgreSQL and AgentRun durable owners'))
186
- const admission = control(profile, 'agents', 'admission')
187
- if (admission === 'pg-boss' && (!enabled(profile, 'jobs') || profile.capabilities.jobs.ownership !== 'owned' || !(control(profile, 'jobs', 'roles') ?? []).includes('agent-admission'))) diagnostics.push(diag('DUR-003', 'agents.admission', profileRel, 'owned agent admission requires owned pg-boss jobs with agent-admission role'))
188
- if (admission === 'shared-contract' && !profile.capabilities.agents.contract && !profile.capabilities.jobs?.contract) diagnostics.push(diag('DUR-003', 'agents.admission', profileRel, 'shared admission requires an explicit qualified service contract'))
189
- if (!['pg-boss', 'shared-contract'].includes(admission)) diagnostics.push(diag('DUR-003', 'agents.admission', profileRel, 'agents must declare pg-boss or shared-contract admission'))
190
- }
191
- if (enabled(profile, 'jobs') && profile.capabilities.jobs.ownership === 'owned' && !control(profile, 'jobs', 'databaseOwner')) diagnostics.push(diag('DUR-003', 'jobs.database-owner', profileRel, 'Owned pg-boss jobs must declare their PostgreSQL owner'))
192
- if (enabled(profile, 'knowledge') && profile.capabilities.knowledge.ownership === 'owned') {
193
- if (!control(profile, 'knowledge', 'postgresOwner')) diagnostics.push(diag('DATA-002', 'knowledge.storage-owner', profileRel, 'Owned knowledge must declare its PostgreSQL/pgvector owner'))
194
- if (control(profile, 'knowledge', 'binaryObjects') === true && !control(profile, 'knowledge', 'objectStorageOwner')) diagnostics.push(diag('DATA-002', 'knowledge.object-storage-owner', profileRel, 'Knowledge with binary objects must declare the object-storage owner'))
195
- }
196
- for (const name of ['connectors', 'modelRouting']) if (enabled(profile, name) && control(profile, name, 'credentialBearing') === true && !enabled(profile, 'secrets')) diagnostics.push(diag('SEC-002', 'secrets.dependency', profileRel, `Credential-bearing ${name} requires an enabled production secret-custody capability`))
197
- if (enabled(profile, 'enterpriseIdentity') && control(profile, 'enterpriseIdentity', 'scim') === true && !enabled(profile, 'secrets')) diagnostics.push(diag('SEC-002', 'secrets.dependency', profileRel, 'SCIM requires an enabled production secret-custody capability'))
198
- if (enabled(profile, 'notifications') && !control(profile, 'notifications', 'durableIntentOwner')) diagnostics.push(diag('RT-005', 'notifications.durable-intent-owner', profileRel, 'Notifications must declare the durable notification-intent owner'))
199
- if (control(profile, 'agents', 'untrustedExecution') === true) {
200
- if (!enabled(profile, 'sandbox')) diagnostics.push(diag('SBX-001', 'sandbox.activation', profileRel, 'untrusted execution activates the sandbox capability'))
201
- if (control(profile, 'sandbox', 'trustedBroker') !== true) diagnostics.push(diag('SBX-002', 'sandbox.trusted-broker', profileRel, 'untrusted execution requires a trusted capability broker'))
202
- }
203
- if (enabled(profile, 'sandbox')) {
204
- if (control(profile, 'sandbox', 'egress') !== 'deny-by-default') diagnostics.push(diag('SBX-003', 'sandbox.egress', profileRel, 'sandbox egress must be deny-by-default'))
205
- if (control(profile, 'sandbox', 'databaseCredentials') !== false) diagnostics.push(diag('SBX-002', 'sandbox.credentials', profileRel, 'sandboxes must not receive database credentials'))
206
- }
207
- if (enabled(profile, 'enterpriseIdentity') && control(profile, 'enterpriseIdentity', 'scim') === true) {
208
- if (control(profile, 'enterpriseIdentity', 'organizationMapping') !== true || control(profile, 'enterpriseIdentity', 'deprovisioning') !== true) diagnostics.push(diag('AUTH-006', 'identity.scim-deprovisioning', profileRel, 'SCIM requires organization mapping and complete deprovisioning'))
209
- }
210
- if (enabled(profile, 'webControlPlane')) {
211
- if (profile.environments?.production?.hosting === 'cloudflare-opennext' && profile.capabilities.webControlPlane.placement !== 'open-next-worker') diagnostics.push(diag('HOST-004', 'hosting.web-placement', profileRel, 'Cloudflare/OpenNext requires web placement open-next-worker'))
212
- if (profile.project?.access !== 'public' && control(profile, 'webControlPlane', 'secureCookies') !== true) diagnostics.push(diag('AUTH-003', 'auth.secure-cookies', profileRel, 'authenticated web access requires secure cookies'))
213
- if (profile.project?.access !== 'public' && profile.capabilities.webControlPlane.ownership === 'owned' && !enabled(profile, 'secrets')) diagnostics.push(diag('SEC-002', 'secrets.activation', profileRel, 'owned authenticated web production activates secret custody'))
214
- if (enabled(profile, 'flutter') && (control(profile, 'webControlPlane', 'canonicalApi') !== 'rest-openapi' || control(profile, 'webControlPlane', 'openapiGenerated') !== true)) diagnostics.push(diag('API-002', 'api.generated-contract', profileRel, 'Flutter requires canonical generated REST/OpenAPI'))
215
- }
216
- if (profile.environments?.production?.hosting === 'cloudflare-opennext') for (const name of ['agents', 'jobs']) {
217
- const cap = profile.capabilities?.[name]
218
- if (cap?.status === 'enabled' && cap.ownership === 'owned' && !['node-service', 'oci-container'].includes(cap.placement)) diagnostics.push(diag(name === 'agents' ? 'RUN-001' : 'RUN-002', `hosting.${name}-placement`, profileRel, `owned ${name} must use external long-running Node/OCI placement outside OpenNext`))
219
- }
220
- if (enabled(profile, 'secrets') && profile.capabilities.secrets.ownership === 'owned' && control(profile, 'secrets', 'provider') !== 'openbao') diagnostics.push(diag('SEC-002', 'secrets.production-provider', profileRel, 'owned production secret custody requires OpenBao'))
221
-
222
- const errors = diagnostics.map(item => `${item.rule}/${item.control}: ${item.message}`)
223
- return { profile, errors, diagnostics, exceptions: exceptionStates }
45
+ for (const message of validateJsonSchema(schema, profile)) errors.push(message)
46
+ for (const location of unconfirmedValues(profile)) errors.push(`${location}: replace the REQUIRED placeholder with a confirmed fact`)
47
+ return { profile, errors }
224
48
  }
225
49
 
226
50
  if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
@@ -234,8 +58,8 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
234
58
  }
235
59
  const json = process.argv.includes('--json')
236
60
  const result = await validateProfile(path)
237
- if (json) console.log(JSON.stringify({ path, valid: result.errors.length === 0, diagnostics: result.diagnostics, exceptions: result.exceptions }, null, 2))
238
- else if (result.errors.length) for (const error of result.errors) console.error(`FAIL profile: ${error}`)
61
+ if (json) console.log(JSON.stringify({ path, valid: result.errors.length === 0, errors: result.errors }, null, 2))
62
+ else if (result.errors.length) for (const error of result.errors) console.error(`invalid profile: ${error}`)
239
63
  else console.log(`validate-profile: valid ${path}`)
240
64
  if (result.errors.length) process.exitCode = 1
241
65
  }
@@ -26,7 +26,6 @@ const chapters = await listFiles(architectureRoot, path => path.endsWith('.md'))
26
26
  const registry = await readJsonYaml(join(skillRoot, 'refresh', 'sources.json'))
27
27
  const sourceIds = new Set(registry.sources.map(source => source.id))
28
28
  const ruleIds = await canonicalRuleIds()
29
- const controlCatalog = await readJsonYaml(join(references, 'control-catalog.json'))
30
29
  const ruleModel = await readJsonYaml(join(references, 'rule-model.json'))
31
30
  const errors = []
32
31
  let diagrams = 0
@@ -86,15 +85,6 @@ for (const source of registry.sources) {
86
85
  }
87
86
  }
88
87
 
89
- const catalogKeys = new Set()
90
- for (const control of controlCatalog.controls ?? []) {
91
- if (!ruleIds.has(control.rule)) errors.push(`control catalog maps unknown rule ${control.rule}`)
92
- if ((!control.id && !control.idPattern) || (control.id && control.idPattern)) errors.push(`control catalog entry for ${control.rule} must have exactly one id or idPattern`)
93
- const key = `${control.rule}/${control.id ?? control.idPattern}`
94
- if (catalogKeys.has(key)) errors.push(`duplicate control catalog entry ${key}`)
95
- catalogKeys.add(key)
96
- for (const required of ['classification', 'activation', 'verification', 'waiver']) if (!control[required]) errors.push(`control catalog ${key} missing ${required}`)
97
- }
98
88
  const modeledRules = new Set()
99
89
  for (const group of ruleModel.groups ?? []) {
100
90
  for (const required of ['activation', 'verification', 'rationale']) if (!group[required]) errors.push(`rule model group missing ${required}`)
@@ -107,9 +97,7 @@ for (const group of ruleModel.groups ?? []) {
107
97
  for (const rule of ruleIds) if (!modeledRules.has(rule)) errors.push(`canonical rule ${rule} has no activation/verification/rationale model`)
108
98
  for (const override of ruleModel.overrides ?? []) if (!modeledRules.has(override.rule)) errors.push(`rule model override names unknown rule ${override.rule}`)
109
99
 
110
- for (const match of (await readFile(join(skillRoot, 'scripts', 'architecture-check.mjs'), 'utf8')).matchAll(/issue\([^,]+,\s*['"]([A-Z]+-[0-9]{3})['"]/g)) if (!ruleIds.has(match[1])) errors.push(`architecture-check emits unknown rule ${match[1]}`)
111
-
112
- const bannedNames = ['golden-architecture.md', 'evidence-manifest.json', 'coverage-matrix.md', 'verification-ledger.md', '23-evidence-comparisons.md', '24-roadmap.md', 'compile-guide.mjs']
100
+ const bannedNames = ['golden-architecture.md', 'evidence-manifest.json', 'coverage-matrix.md', 'verification-ledger.md', '23-evidence-comparisons.md', '24-roadmap.md', 'compile-guide.mjs', 'architecture-check.mjs', 'control-catalog.json']
113
101
  const allFiles = await listFiles(skillRoot)
114
102
  for (const path of allFiles) if (bannedNames.includes(basename(path))) errors.push(`forbidden runtime artifact remains: ${relative(skillRoot, path)}`)
115
103
  const runtimeText = (await Promise.all((await listFiles(skillRoot, path => /\.(?:md|json|yaml|yml|mjs)$/.test(path) && !path.includes('/tests/'))).map(path => readFile(path, 'utf8')))).join('\n')
@@ -19,7 +19,7 @@ The deterministic runner is repo-shared and hosted in arch-guardian for now. Run
19
19
  `node skills/arch-guardian/scripts/refresh-evidence.mjs --registry skills/skill-maintainer/refresh/sources.json --accept-baselines`
20
20
  This writes registry, cache, and drift report together — never hand-edit checksums, versions, or timestamps; they must always come from a run. Baselines are runner-seeded; when a new source is added or a verified change is accepted, this accept-baselines invocation is the only sanctioned way to update them.
21
21
  3. **Semantic verification** for every source the deterministic pass flagged: read the changed page (fetch the registry URL), decide whether any `<!-- source: X -->` marked sentence in `references/standards.md` (or the mirrored SKILL.md hard-limits row) is now wrong, and propose the minimal edit. Unlike version-pin registries, checksum drift here is presumed meaningful until a human reads the diff — these pages define the standards themselves. Editorial churn may be accepted silently only after that read.
22
- 4. **One PR per refresh**, branch `refresh/<date>`, force-updating the standing refresh branch if last week's PR is unmerged (never stack duplicate PRs). PR body lists: each changed source, old→new checksum, links to the evidence, and which marked sentences changed and why. A maintainer review is mandatory before merge.
22
+ 4. **One standing refresh PR**, branch `refresh/weekly`, force-updated on every run (never stacked duplicates; the weekly workflow .github/workflows/refresh.yml maintains it). PR body lists: each changed source, old→new checksum, links to the evidence, and which marked sentences changed and why. A maintainer review is mandatory before merge.
23
23
  5. Changes to durable content, workflows, tests, or anything outside `refresh/` and marked sentences are out of scope for a refresh PR — CI enforces this (refresh-guard workflow).
24
24
 
25
25
  ## Cadence and thresholds
@@ -3,48 +3,47 @@
3
3
  "skills": {
4
4
  "arch-guardian": {
5
5
  "files": {
6
- "SKILL.md": "772371cbfb284719de25d0bb9e9b3a5b37e4405182d626200c55e38404f6f140",
7
- "agents/openai.yaml": "4f4b8216f623dd90e55a23e508186403f7353919a6b25551391920416943ac35",
8
- "assets/adr-template.md": "7a7138cfc7fe39427e60003b83eb41ea8586dc8a9e0d56e6d7dafda044e10ed8",
9
- "assets/answers-example.json": "5eb2543f1c1d439b8cbb61e15d1634aac1ba92fdd907a1e8592c8a98d75abf56",
10
- "assets/architecture-profile.json": "c9f776c5d17f8766d0c9b22723f567ac8e3821c4ea63add5d15877652ec98bdd",
11
- "assets/architecture-profile.schema.json": "0948c5f880f2b46d5d9ead211f23f0a23c3fc86d4c14e8de69beb7edbe06713a",
6
+ "SKILL.md": "46eb5d40506c3e7b2f9d15a3cbfa189acb690ba55e7af924d463e003ae8eeb70",
7
+ "agents/openai.yaml": "9553ddb318a4b7f0d610343f28fc8af636fc4b24832a00e818c3b824449b0a59",
8
+ "assets/adr-template.md": "db813ef4621a76e1101b3a19fc944de60a38591b423d9a9ad6c8f0b1db38120c",
9
+ "assets/answers-example.json": "eed87446717032c60015232674272689bd68a7c8f5f43ff166aeaa0f649b7a9f",
10
+ "assets/architecture-profile.json": "70d78d81f2c65f7aa37d9ea7b7a5f0518961037262ae2db6da8e6c1f24757ee8",
11
+ "assets/architecture-profile.schema.json": "bcf370b882ef6e605abf530c8245d599471e8aa54f0ef42fc99b5119550b8905",
12
12
  "assets/deployment-review-template.md": "e9be2feb153138d936f9ebd5763c3be10597cee639616f86cc082c498fd60cd2",
13
13
  "assets/service-design-template.md": "670798e3bf26a76d593294ff60763e1a6a35866fa84ea74345406b05b4b0b61b",
14
14
  "assets/threat-model-template.md": "ffcf22e3fc81c803d976ac6c41cbac4fb0f552d81653b89a5b587a46b59d9aed",
15
+ "references/advisory-report.md": "bfc0dc8e985e33312ed0617705b359457eaea7b6056307e94ea293d167992dd4",
15
16
  "references/architecture/agent-product.md": "3b8039ef9ca6aa30fc8781475e005d7bbb9d8d415705a6e8c79b4a602ee1ed5e",
16
- "references/architecture/ai-cost.md": "2892d1ed68c19561fb4f38956bbbd7b573c6544f220e8cb88be0dc57bf757221",
17
- "references/architecture/ai-data-boundaries.md": "d5b2e6486ac2662a9bab1e0469c5ff9dcc00ec56cf8d190dd025f62178dc97fa",
18
- "references/architecture/ai-evals.md": "cfd81abac927292e6fb3dc2bf999754fc6f30a41184dafcf1060ac3a16f0cd30",
19
- "references/architecture/connectors-sandbox.md": "5d410e4ec06463b6a51b941fdef768f22f8f62f132117d05a10d125822248621",
17
+ "references/architecture/ai-cost.md": "9cb300e5718c15d5e6c29d22293b4ca80340e73d2b77267fbc529016c6262dd2",
18
+ "references/architecture/ai-data-boundaries.md": "0d756d0132d05e0e45dcd1b582e6c4bfe04a9043ab8df64d9a88fb086d8b29ff",
19
+ "references/architecture/ai-evals.md": "9b9698acd8b73c17ea301569b6c57bbdede304cfec7fa6f7d6d4af017a64fa6c",
20
+ "references/architecture/connectors-sandbox.md": "c12248cb0eddf51143e6f14c71541042d676b405bf274271bb69148c621ab41f",
20
21
  "references/architecture/data-memory.md": "3d2ccbcd90a00dddda7096877c874d4fa7b61b96e2e4061c3c82b40bd990cf19",
21
- "references/architecture/delivery-operations.md": "2988ae173517faaaff325d39c2881f854663a0386f073475b479a66bb8c69008",
22
- "references/architecture/durable-execution.md": "a26420ca9fac928597e09ad88aefa221034fef597113335537328e8c9c4df8ea",
23
- "references/architecture/flutter.md": "434365ff03620b192444af2052f723fb2cb05ffe702894292aaeca72b19b3da0",
24
- "references/architecture/foundation.md": "bd6eb424139e82f84a7787bd1f23e8206c78fd85e80d81a0f57796f55d8c8b6e",
25
- "references/architecture/hosting-reliability.md": "10d9a3a82f688c1acd1a1c9a3fbcdcb516217ce446ea09b2fe326b0a8e93b015",
26
- "references/architecture/identity-tenancy.md": "8c6acc3ec474072d92de59465d6d528c8ca247507ca158f492f405f63b863723",
27
- "references/architecture/model-lifecycle.md": "061f79418e732cd32efca703f0a6f7c04132ddc8063b2719ce2e3af93efd575f",
28
- "references/architecture/models-observability.md": "d81e0a8e26d9065f8e26b385a52fade1cf5cd9f82549e8586dd8396eabd0d203",
22
+ "references/architecture/delivery-operations.md": "82fafe5f0f157f7a870ff6c9aad64564ab121ac049446318fcf9c1b7f4517f36",
23
+ "references/architecture/durable-execution.md": "4dd1c7d69e41fb0abf7f469c8eadd1695b15c502e0219108c76d8aea06f9ffe6",
24
+ "references/architecture/flutter.md": "f024292b89b4667c65993bba906fb16afd278824f205f0e39c96a58384c6f423",
25
+ "references/architecture/foundation.md": "19536e03fdbb8720e263600a298b11e980cf6c24a724f8692db83b04302b39f1",
26
+ "references/architecture/hosting-reliability.md": "c69b00a354d78e2a7b2845cddc0d8a642911bc009efefa141075d1d774756142",
27
+ "references/architecture/identity-tenancy.md": "5fb2e5d74ae24ad2efe3ba01c06b8fcc16ba1dce1b9c531b7d955352af38d4c2",
28
+ "references/architecture/model-lifecycle.md": "eacdeade0fa88caaca11de9a9e69f0db8a9ecfc8b472070afb7158f5e30f6ba2",
29
+ "references/architecture/models-observability.md": "5501655b71d85e9653e5a8c276e481e2765b2a0c67c5bc23759f869529278b94",
29
30
  "references/architecture/realtime-channels.md": "a95738c431f3f4c8c42c153a0b940897bae8d679ed0c2dfced3cb1c4c9c67ca5",
30
- "references/architecture/security-privacy.md": "68d00b7accf861664bf26acadd7e88af5bc64e78b04a4fe506932366945d39e5",
31
- "references/architecture/topology-monorepo.md": "18763c997b924dbc915f49a69712cec95bf8a2e32c67901e12b515d90e698436",
32
- "references/architecture/web.md": "7b1cf9f459de297c2eda6582ecf5af3727010b15cb31069ee5c424023c9109c4",
33
- "references/control-catalog.json": "6d5135b4cc209bc9321e8946b6b8c46ddc04532dc4cfb798ed4d7eedbb12f64e",
34
- "references/foundation-compatibility.json": "5f6c278e601f9221c55b049622da33c613ac274116d939dc479e800b09dbb7a5",
31
+ "references/architecture/security-privacy.md": "28d8d1a6347df95807d274829b2fbbd852c07a74d52d7e0be18d208b559504ea",
32
+ "references/architecture/topology-monorepo.md": "fdad26c8eaa32b977139e6f70c90a3954332a1fa6dd6e8d98bc9fb7c5a613117",
33
+ "references/architecture/web.md": "560957d179f9372ff01e3e216fdc8ad1cbee290ac95f42094d04da4af08a4c0a",
34
+ "references/foundation-compatibility.json": "dfbf106631ebd229d683b18855e126cdc4684a252038123b25d002724571ac14",
35
35
  "references/golden-patterns.md": "0b00ece15e0ae20450d5c20409e7be34bab0d9b47c1614fb48cef780ca66605b",
36
- "references/profile-governance.md": "14821377e0018072e3843da9d26001040c3b771b3ecf18052859ff77a967a97e",
37
- "references/rule-model.json": "6429a29c3a106a9851f326dfc963483115a62a508a57772c586951f6726cf8e0",
38
- "references/workflows.md": "e17858b8f41fddf47a40076e7add3fa7f10aa1dad87fb14f07fae108bc97d2de",
39
- "refresh/REFRESH.md": "14e3d8b3798e9a98cb8a279e2c4cb641faf867b4a820fa9be7b354f6c15dd090",
40
- "refresh/sources.json": "8c43fb27cb5beb3d8e52ef208b2e22bc1781b298bdcd779114be188f7ca64f23",
41
- "scripts/architecture-check.mjs": "37866fdfe4a6a5de3068144536ccfd8b1351c66793b57cf432ce1bf2444c52b4",
42
- "scripts/lib.mjs": "ae0c8a324c960ba6af95054d0458e276d6122c835c3094500963078b2202c24b",
43
- "scripts/profile-tool.mjs": "c79862b55935e2c472ea943c7365815a13b18c22d7783b9beee89b6b8cc1bb33",
44
- "scripts/refresh-evidence.mjs": "3cf7734d7d90260a3ab5f4340a62b581a085f73369fd40da51e6fe5bf54a2387",
36
+ "references/profile-governance.md": "b3ed0b4580c73fa0340c33267707ee9515071ac2b64bdc3dd56b8c558431a0b3",
37
+ "references/rule-model.json": "b9d4d13d152eecbb73a5add12c8ab9bb050e0f1001495c90c8b801d029b56598",
38
+ "references/workflows.md": "f54c284d831e14da00886fd186bc711bb8598fca4f2fc309f6caa197835e79ae",
39
+ "refresh/REFRESH.md": "db15a4304d0a3d3ab7f53722d30fb76609f1823cb2dc998f1807f092e79e008a",
40
+ "refresh/sources.json": "d35735620325c45693c8eddb5de5e7e1867cae19554e118b45867f8984250950",
41
+ "scripts/lib.mjs": "4ef4910518134eea7f89b6e397e5871f66a416429c75f59bf37e0864e4cf7ca0",
42
+ "scripts/profile-tool.mjs": "c13230b1c6a7bce9b91fb8a6a7d6eb84e9ef8855bf8b2a544e7bbfdc38e94877",
43
+ "scripts/refresh-evidence.mjs": "4759cedd15470247df23485b4c8d258e7e8f22cbceaac60f7e63cb42159af779",
45
44
  "scripts/schema-validate.mjs": "329cca29ead284949107a5e0984588373522e9ef6624dffd18d4d207ef51fc99",
46
- "scripts/validate-profile.mjs": "f823ce2a04b12f33e6c459e179453d988e5b7b1a888fbf00ad3ee36a18fa2ba7",
47
- "scripts/verify-corpus.mjs": "d33d3f1717f988a45fe50477d5ad357a7c026abc75d3c1c23812d096b7dfe633"
45
+ "scripts/validate-profile.mjs": "0b1f6b6d9e21267fd1ac545fd47fe46d9ecddc6a2efe67c5518e0845719a2b4d",
46
+ "scripts/verify-corpus.mjs": "d752efb00145afeaa7d2e758e10c5c11670253c1387326a52d6dda7fc67cda1c"
48
47
  }
49
48
  },
50
49
  "skill-maintainer": {
@@ -53,7 +52,7 @@
53
52
  "agents/openai.yaml": "0b89d4e6416cf20b448f5322f3d7433cfde333d2b4a05b42ed53db3759701fb4",
54
53
  "references/release-ops.md": "2bde6c55cbe7674678e14b915fc5ebe565413aeb25c3d51a86e195af53004d01",
55
54
  "references/standards.md": "3646d0dd0560a87b333f70febf670b093c89b35111132cdb641b763bf184b02f",
56
- "refresh/REFRESH.md": "f7aef495c6e7015c95535c9ea0136792b23c83a94e7942a5d5d1e2e415e0cc89",
55
+ "refresh/REFRESH.md": "a9a86a979ef720cf2f316c76f05db0fd9beef591401b95ceb475474fe40bc936",
57
56
  "refresh/sources.json": "cf38cdf8123a50ea7ef2d1466a87faa648c12e5bc90d9ee9b6ae45632e7d0800"
58
57
  }
59
58
  },
@@ -1,55 +0,0 @@
1
- {
2
- "schemaVersion": 1,
3
- "description": "Atomic checker/validator controls. Normative prose remains canonical in architecture references.",
4
- "controls": [
5
- { "rule": "FOUND-001", "idPattern": "^profile\\.(?:present|parse|schema|schema-version|confirmed|project-name)$", "classification": "invariant", "activation": "profile validation or CI", "verification": "structural/semantic", "waiver": "project-adr" },
6
- { "rule": "FOUND-002", "idPattern": "^exception\\.(?:validity|scope-match)$", "classification": "invariant", "activation": "exception declared", "verification": "semantic/filesystem", "waiver": "project-adr" },
7
- { "rule": "FOUND-004", "idPattern": "^(?:capability\\.[A-Za-z]+\\.(?:source-root|undeclared|missing-code)|profile\\.capability-alignment)$", "classification": "invariant", "activation": "capability declared or observed", "verification": "static/semantic", "waiver": "project-adr" },
8
- { "rule": "PKG-003", "idPattern": "^(?:foundation\\.(?:baseline|adoption|non-supported-adoption)|version\\.[A-Za-z]+\\.[A-Za-z0-9]+)$", "classification": "invariant", "activation": "versioned capability enabled", "verification": "semantic", "waiver": "project-adr" },
9
- { "rule": "RUN-001", "idPattern": "^(?:runtime\\.web-bundle-boundary|hosting\\.agents-placement)$", "classification": "forbidden-design", "activation": "owned agents", "verification": "static/semantic", "waiver": "project-adr" },
10
- { "rule": "RUN-002", "idPattern": "^(?:runtime\\.web-bundle-boundary|hosting\\.jobs-placement)$", "classification": "forbidden-design", "activation": "owned jobs", "verification": "static/semantic", "waiver": "project-adr" },
11
- { "rule": "RUN-003", "idPattern": "^(?:capability\\.source-root-overlap|contract\\.[A-Za-z]+\\.qualification)$", "classification": "invariant", "activation": "provider boundary or shared/external capability", "verification": "semantic/manual", "waiver": "project-adr" },
12
- { "rule": "HOST-004", "id": "hosting.web-placement", "classification": "invariant", "activation": "Cloudflare/OpenNext web", "verification": "semantic", "waiver": "project-adr" },
13
- { "rule": "MOB-001", "id": "flutter.oauth-pkce", "classification": "invariant", "activation": "Flutter enabled", "verification": "semantic/runtime", "waiver": "project-adr" },
14
- { "rule": "MOB-002", "idPattern": "^flutter\\.(?:api-provider|generated-client)$", "classification": "invariant", "activation": "Flutter enabled", "verification": "semantic/generated-contract", "waiver": "project-adr" },
15
- { "rule": "DUR-001", "idPattern": "^(?:agents\\.(?:durable-owner|storage-ownership|dual-workflow-state)|jobs\\.workflow-ownership)$", "classification": "invariant", "activation": "agents enabled or observed", "verification": "static/semantic/runtime", "waiver": "project-adr" },
16
- { "rule": "DUR-003", "idPattern": "^(?:agents\\.(?:admission|pg-boss-ownership)|jobs\\.database-owner)$", "classification": "invariant", "activation": "agent admission or owned jobs", "verification": "static/semantic/runtime", "waiver": "project-adr" },
17
- { "rule": "DATA-002", "idPattern": "^knowledge\\.(?:storage-owner|object-storage-owner)$", "classification": "preferred-default", "activation": "owned knowledge", "verification": "semantic/runtime", "waiver": "project-adr" },
18
- { "rule": "SBX-001", "id": "sandbox.activation", "classification": "invariant", "activation": "untrusted execution", "verification": "semantic/runtime", "waiver": "project-adr" },
19
- { "rule": "SBX-002", "idPattern": "^sandbox\\.(?:trusted-broker|credentials)$", "classification": "invariant", "activation": "sandbox enabled", "verification": "semantic/runtime", "waiver": "project-adr" },
20
- { "rule": "SBX-003", "id": "sandbox.egress", "classification": "forbidden-design", "activation": "sandbox enabled or observed", "verification": "static/runtime", "waiver": "project-adr" },
21
- { "rule": "AUTH-001", "id": "auth.organization-boundary", "classification": "invariant", "activation": "authenticated multi-tenant product", "verification": "static/runtime", "waiver": "project-adr" },
22
- { "rule": "AUTH-003", "idPattern": "^(?:auth\\.(?:secure-cookies|secure-cookies-code|library|csrf-origin|trusted-origins))$", "classification": "invariant", "activation": "authenticated web", "verification": "static/semantic/runtime", "waiver": "project-adr" },
23
- { "rule": "AUTH-004", "idPattern": "^auth\\.(?:pkce|oauth-provider)$", "classification": "invariant", "activation": "delegated client", "verification": "static/runtime", "waiver": "project-adr" },
24
- { "rule": "AUTH-005", "id": "auth.api-key-scope", "classification": "forbidden-design", "activation": "API keys observed", "verification": "static/runtime", "waiver": "project-adr" },
25
- { "rule": "AUTH-006", "idPattern": "^identity\\.(?:scim-deprovisioning|scim-storage)$", "classification": "invariant", "activation": "SCIM enabled or observed", "verification": "static/semantic/runtime", "waiver": "project-adr" },
26
- { "rule": "API-002", "id": "api.generated-contract", "classification": "invariant", "activation": "Flutter/public client enabled", "verification": "semantic/generated-contract", "waiver": "project-adr" },
27
- { "rule": "TEN-001", "idPattern": "^tenancy\\.composite-(?:primary-key|relationship)$", "classification": "invariant", "activation": "shared-schema multi-tenancy", "verification": "static/runtime", "waiver": "project-adr" },
28
- { "rule": "TEN-002", "idPattern": "^tenancy\\.rls-(?:enable|force|policy|evidence)$", "classification": "invariant", "activation": "shared-schema multi-tenancy", "verification": "static/runtime", "waiver": "project-adr" },
29
- { "rule": "TEN-003", "idPattern": "^tenancy\\.(?:context-local|role-bypassrls|context-evidence)$", "classification": "forbidden-design", "activation": "shared-schema multi-tenancy or tenant context observed", "verification": "static/runtime", "waiver": "project-adr" },
30
- { "rule": "TEN-004", "id": "tenancy.security-definer", "classification": "forbidden-design", "activation": "privileged shared-schema path", "verification": "static/runtime", "waiver": "project-adr" },
31
- { "rule": "SEC-002", "idPattern": "^(?:secret\\.(?:plaintext|bearer)|secrets\\.(?:activation|production-provider|dependency))$", "classification": "forbidden-design", "activation": "production secrets declared or observed", "verification": "static/semantic/runtime", "waiver": "project-adr" },
32
- { "rule": "OBS-002", "id": "telemetry.sensitive-data", "classification": "forbidden-design", "activation": "telemetry call observed", "verification": "static/runtime", "waiver": "project-adr" },
33
- { "rule": "RT-002", "id": "realtime.websocket-trigger", "classification": "preferred-default", "activation": "WebSockets observed", "verification": "manual-qualification", "waiver": "project-adr" },
34
- { "rule": "RT-005", "id": "notifications.durable-intent-owner", "classification": "invariant", "activation": "notifications enabled", "verification": "semantic/runtime", "waiver": "project-adr" },
35
- { "rule": "REL-001", "id": "reliability.objectives", "classification": "invariant", "activation": "production project", "verification": "declared/manual", "waiver": "project-adr" },
36
- { "rule": "DEL-001", "idPattern": "^(?:inspection\\.(?:traversal|read)|qualification\\.runtime)$", "classification": "invariant", "activation": "review or qualification", "verification": "static/manual/runtime", "waiver": "project-adr" },
37
- { "rule": "DUR-009", "id": "agents.exit-contract", "classification": "invariant", "activation": "owned durable execution in production", "verification": "manual-qualification", "waiver": "project-adr" },
38
- { "rule": "EVAL-001", "id": "evals.definition", "classification": "invariant", "activation": "model-backed behavior shipped to users", "verification": "manual-qualification", "waiver": "project-adr" },
39
- { "rule": "EVAL-002", "id": "evals.promotion-gate", "classification": "invariant", "activation": "agent/prompt/model change promoted", "verification": "manual-qualification", "waiver": "project-adr" },
40
- { "rule": "EVAL-003", "id": "evals.dataset-versioning", "classification": "invariant", "activation": "eval datasets exist", "verification": "manual-qualification", "waiver": "project-adr" },
41
- { "rule": "EVAL-004", "id": "evals.online-sampling", "classification": "invariant", "activation": "production eval sampling enabled", "verification": "manual-qualification", "waiver": "project-adr" },
42
- { "rule": "MLIFE-001", "id": "models.pinned-ids", "classification": "invariant", "activation": "external model providers enabled", "verification": "manual-qualification", "waiver": "project-adr" },
43
- { "rule": "MLIFE-002", "id": "models.deprecation-watch", "classification": "invariant", "activation": "external model providers enabled", "verification": "manual-qualification", "waiver": "project-adr" },
44
- { "rule": "MLIFE-003", "id": "models.canary-rollout", "classification": "preferred-default", "activation": "prompt+model behavior change", "verification": "manual-qualification", "waiver": "project-adr" },
45
- { "rule": "MLIFE-004", "id": "models.backpressure", "classification": "invariant", "activation": "external model providers enabled", "verification": "manual-qualification", "waiver": "project-adr" },
46
- { "rule": "COST-001", "id": "cost.workspace-attribution", "classification": "invariant", "activation": "metered AI resources exist", "verification": "manual-qualification", "waiver": "project-adr" },
47
- { "rule": "COST-002", "id": "cost.budgets-alerts", "classification": "invariant", "activation": "production AI features", "verification": "manual-qualification", "waiver": "project-adr" },
48
- { "rule": "COST-003", "id": "cost.cache-economics", "classification": "preferred-default", "activation": "prompt/context caching enabled", "verification": "manual-qualification", "waiver": "project-adr" },
49
- { "rule": "COST-004", "id": "cost.regression-gate", "classification": "invariant", "activation": "AI feature deploy", "verification": "manual-qualification", "waiver": "project-adr" },
50
- { "rule": "PII-001", "id": "pii.ingestion-redaction", "classification": "invariant", "activation": "knowledge or embedding ingestion", "verification": "manual-qualification", "waiver": "project-adr" },
51
- { "rule": "PII-002", "id": "pii.prompt-boundary", "classification": "invariant", "activation": "external model calls with personal data", "verification": "manual-qualification", "waiver": "project-adr" },
52
- { "rule": "PII-003", "id": "pii.output-moderation", "classification": "invariant", "activation": "public-facing AI output", "verification": "manual-qualification", "waiver": "project-adr" },
53
- { "rule": "PII-004", "id": "pii.injection-mitigations", "classification": "invariant", "activation": "tool results, retrieval, or connector content in prompts", "verification": "manual-qualification", "waiver": "project-adr" }
54
- ]
55
- }