@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.
- package/README.md +1 -1
- package/dist/index.js +9 -9
- package/package.json +1 -1
- package/skill/arch-guardian/SKILL.md +27 -39
- package/skill/arch-guardian/agents/openai.yaml +2 -2
- package/skill/arch-guardian/assets/adr-template.md +10 -25
- package/skill/arch-guardian/assets/answers-example.json +8 -18
- package/skill/arch-guardian/assets/architecture-profile.json +10 -22
- package/skill/arch-guardian/assets/architecture-profile.schema.json +13 -195
- package/skill/arch-guardian/references/advisory-report.md +65 -0
- package/skill/arch-guardian/references/architecture/ai-cost.md +2 -0
- package/skill/arch-guardian/references/architecture/ai-data-boundaries.md +2 -0
- package/skill/arch-guardian/references/architecture/ai-evals.md +2 -0
- package/skill/arch-guardian/references/architecture/connectors-sandbox.md +2 -2
- package/skill/arch-guardian/references/architecture/delivery-operations.md +5 -5
- package/skill/arch-guardian/references/architecture/durable-execution.md +3 -1
- package/skill/arch-guardian/references/architecture/flutter.md +1 -1
- package/skill/arch-guardian/references/architecture/foundation.md +17 -19
- package/skill/arch-guardian/references/architecture/hosting-reliability.md +1 -1
- package/skill/arch-guardian/references/architecture/identity-tenancy.md +7 -7
- package/skill/arch-guardian/references/architecture/model-lifecycle.md +5 -3
- package/skill/arch-guardian/references/architecture/models-observability.md +1 -1
- package/skill/arch-guardian/references/architecture/security-privacy.md +9 -5
- package/skill/arch-guardian/references/architecture/topology-monorepo.md +1 -1
- package/skill/arch-guardian/references/architecture/web.md +1 -1
- package/skill/arch-guardian/references/foundation-compatibility.json +1 -1
- package/skill/arch-guardian/references/profile-governance.md +25 -39
- package/skill/arch-guardian/references/rule-model.json +2 -2
- package/skill/arch-guardian/references/workflows.md +15 -12
- package/skill/arch-guardian/refresh/REFRESH.md +9 -2
- package/skill/arch-guardian/refresh/sources.json +26 -14
- package/skill/arch-guardian/scripts/lib.mjs +1 -10
- package/skill/arch-guardian/scripts/profile-tool.mjs +43 -49
- package/skill/arch-guardian/scripts/refresh-evidence.mjs +43 -5
- package/skill/arch-guardian/scripts/validate-profile.mjs +20 -196
- package/skill/arch-guardian/scripts/verify-corpus.mjs +1 -13
- package/skill/skill-maintainer/refresh/REFRESH.md +1 -1
- package/skill-integrity.json +34 -35
- package/skill/arch-guardian/references/control-catalog.json +0 -55
- package/skill/arch-guardian/scripts/architecture-check.mjs +0 -323
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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,
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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]
|
|
38
|
+
return { profile: null, errors: [error.message] }
|
|
131
39
|
}
|
|
132
|
-
if (profile.schemaVersion === 2) {
|
|
133
|
-
|
|
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))
|
|
138
|
-
const
|
|
139
|
-
|
|
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,
|
|
238
|
-
else if (result.errors.length) for (const error of result.errors) console.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
|
-
|
|
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
|
|
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
|
package/skill-integrity.json
CHANGED
|
@@ -3,48 +3,47 @@
|
|
|
3
3
|
"skills": {
|
|
4
4
|
"arch-guardian": {
|
|
5
5
|
"files": {
|
|
6
|
-
"SKILL.md": "
|
|
7
|
-
"agents/openai.yaml": "
|
|
8
|
-
"assets/adr-template.md": "
|
|
9
|
-
"assets/answers-example.json": "
|
|
10
|
-
"assets/architecture-profile.json": "
|
|
11
|
-
"assets/architecture-profile.schema.json": "
|
|
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": "
|
|
17
|
-
"references/architecture/ai-data-boundaries.md": "
|
|
18
|
-
"references/architecture/ai-evals.md": "
|
|
19
|
-
"references/architecture/connectors-sandbox.md": "
|
|
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": "
|
|
22
|
-
"references/architecture/durable-execution.md": "
|
|
23
|
-
"references/architecture/flutter.md": "
|
|
24
|
-
"references/architecture/foundation.md": "
|
|
25
|
-
"references/architecture/hosting-reliability.md": "
|
|
26
|
-
"references/architecture/identity-tenancy.md": "
|
|
27
|
-
"references/architecture/model-lifecycle.md": "
|
|
28
|
-
"references/architecture/models-observability.md": "
|
|
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": "
|
|
31
|
-
"references/architecture/topology-monorepo.md": "
|
|
32
|
-
"references/architecture/web.md": "
|
|
33
|
-
"references/
|
|
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": "
|
|
37
|
-
"references/rule-model.json": "
|
|
38
|
-
"references/workflows.md": "
|
|
39
|
-
"refresh/REFRESH.md": "
|
|
40
|
-
"refresh/sources.json": "
|
|
41
|
-
"scripts/
|
|
42
|
-
"scripts/
|
|
43
|
-
"scripts/
|
|
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": "
|
|
47
|
-
"scripts/verify-corpus.mjs": "
|
|
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": "
|
|
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
|
-
}
|