@vegastack/skills 0.1.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 +27 -7
- package/dist/index.js +195 -99
- package/package.json +16 -5
- package/skill/arch-guardian/SKILL.md +84 -0
- package/skill/arch-guardian/agents/openai.yaml +4 -0
- package/skill/arch-guardian/assets/adr-template.md +25 -0
- package/skill/arch-guardian/assets/answers-example.json +10 -0
- package/skill/arch-guardian/assets/architecture-profile.json +13 -0
- package/skill/arch-guardian/assets/architecture-profile.schema.json +31 -0
- package/skill/arch-guardian/references/advisory-report.md +65 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/ai-cost.md +2 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/ai-data-boundaries.md +2 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/ai-evals.md +2 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/connectors-sandbox.md +2 -2
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/delivery-operations.md +5 -5
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/durable-execution.md +3 -1
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/flutter.md +1 -1
- package/skill/arch-guardian/references/architecture/foundation.md +31 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/hosting-reliability.md +1 -1
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/identity-tenancy.md +7 -7
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/model-lifecycle.md +5 -3
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/models-observability.md +1 -1
- package/skill/arch-guardian/references/architecture/security-privacy.md +27 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/topology-monorepo.md +1 -1
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/web.md +1 -1
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/foundation-compatibility.json +1 -1
- package/skill/arch-guardian/references/profile-governance.md +40 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/rule-model.json +2 -2
- package/skill/{vegastack-arch-guardian → arch-guardian}/references/workflows.md +15 -12
- package/skill/{vegastack-arch-guardian → arch-guardian}/refresh/REFRESH.md +10 -3
- package/skill/{vegastack-arch-guardian → arch-guardian}/refresh/sources.json +34 -22
- package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/lib.mjs +1 -10
- package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/profile-tool.mjs +43 -49
- package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/refresh-evidence.mjs +46 -5
- package/skill/arch-guardian/scripts/validate-profile.mjs +65 -0
- package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/verify-corpus.mjs +1 -13
- package/skill/skill-maintainer/SKILL.md +79 -0
- package/skill/skill-maintainer/agents/openai.yaml +4 -0
- package/skill/skill-maintainer/references/release-ops.md +63 -0
- package/skill/skill-maintainer/references/standards.md +92 -0
- package/skill/skill-maintainer/refresh/REFRESH.md +40 -0
- package/skill/skill-maintainer/refresh/sources.json +103 -0
- package/skill/skillify/SKILL.md +154 -0
- package/skill/skillify/agents/openai.yaml +4 -0
- package/skill/skillify/assets/templates/README.md.template +26 -0
- package/skill/skillify/assets/templates/REFRESH.md.template +21 -0
- package/skill/skillify/assets/templates/SKILL.md.template +26 -0
- package/skill/skillify/assets/templates/openai.yaml.template +4 -0
- package/skill/skillify/assets/templates/skill.test.ts.template +40 -0
- package/skill/skillify/assets/templates/sources.json.template +6 -0
- package/skill/skillify/references/authoring.md +72 -0
- package/skill/skillify/references/eval-playbook.md +62 -0
- package/skill/skillify/refresh/REFRESH.md +24 -0
- package/skill/skillify/refresh/sources.json +6 -0
- package/skill/skillify/scripts/scaffold-skill.mjs +138 -0
- package/skill-integrity.json +74 -45
- package/skill/vegastack-arch-guardian/SKILL.md +0 -96
- package/skill/vegastack-arch-guardian/agents/openai.yaml +0 -4
- package/skill/vegastack-arch-guardian/assets/adr-template.md +0 -40
- package/skill/vegastack-arch-guardian/assets/answers-example.json +0 -20
- package/skill/vegastack-arch-guardian/assets/architecture-profile.json +0 -25
- package/skill/vegastack-arch-guardian/assets/architecture-profile.schema.json +0 -213
- package/skill/vegastack-arch-guardian/references/architecture/foundation.md +0 -33
- package/skill/vegastack-arch-guardian/references/architecture/security-privacy.md +0 -23
- package/skill/vegastack-arch-guardian/references/control-catalog.json +0 -55
- package/skill/vegastack-arch-guardian/references/profile-governance.md +0 -54
- package/skill/vegastack-arch-guardian/scripts/architecture-check.mjs +0 -323
- package/skill/vegastack-arch-guardian/scripts/validate-profile.mjs +0 -241
- /package/skill/{vegastack-arch-guardian → arch-guardian}/assets/deployment-review-template.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/assets/service-design-template.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/assets/threat-model-template.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/agent-product.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/data-memory.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/realtime-channels.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/references/golden-patterns.md +0 -0
- /package/skill/{vegastack-arch-guardian → arch-guardian}/scripts/schema-validate.mjs +0 -0
|
@@ -1,241 +0,0 @@
|
|
|
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'
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
5
|
-
import { listFiles, pathExists, readJsonYaml, resolveProfile } from './lib.mjs'
|
|
6
|
-
import { validateJsonSchema } from './schema-validate.mjs'
|
|
7
|
-
|
|
8
|
-
const skillRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
9
|
-
const architectureRoot = join(skillRoot, 'references', 'architecture')
|
|
10
|
-
const schemaPath = join(skillRoot, 'assets', 'architecture-profile.schema.json')
|
|
11
|
-
const compatibilityPath = join(skillRoot, 'references', 'foundation-compatibility.json')
|
|
12
|
-
|
|
13
|
-
export async function canonicalRuleIds() {
|
|
14
|
-
const ids = new Set()
|
|
15
|
-
const duplicates = new Set()
|
|
16
|
-
for (const path of await listFiles(architectureRoot, file => file.endsWith('.md'))) {
|
|
17
|
-
const body = await readFile(path, 'utf8')
|
|
18
|
-
for (const match of body.matchAll(/\*\*([A-Z]+-[0-9]{3}) —/g)) {
|
|
19
|
-
if (ids.has(match[1])) duplicates.add(match[1])
|
|
20
|
-
ids.add(match[1])
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
if (duplicates.size) throw new Error(`Duplicate canonical rule IDs: ${[...duplicates].join(', ')}`)
|
|
24
|
-
return ids
|
|
25
|
-
}
|
|
26
|
-
|
|
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
|
|
97
|
-
}
|
|
98
|
-
|
|
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)
|
|
128
|
-
let profile
|
|
129
|
-
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: [] }
|
|
131
|
-
}
|
|
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: [] }
|
|
135
|
-
}
|
|
136
|
-
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 }
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
227
|
-
const argument = process.argv.slice(2).find(value => !value.startsWith('-'))
|
|
228
|
-
let path = argument ? resolve(argument) : null
|
|
229
|
-
if (!path) {
|
|
230
|
-
const discovered = await resolveProfile(process.cwd())
|
|
231
|
-
if (!discovered) { console.error('error: no .vegastack/architecture.json (or legacy .yaml) profile found; pass a path explicitly'); process.exit(2) }
|
|
232
|
-
if (discovered.legacy) console.error(`deprecation: ${discovered.relative} uses the legacy .yaml name for a JSON document; rename to .vegastack/architecture.json`)
|
|
233
|
-
path = discovered.path
|
|
234
|
-
}
|
|
235
|
-
const json = process.argv.includes('--json')
|
|
236
|
-
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}`)
|
|
239
|
-
else console.log(`validate-profile: valid ${path}`)
|
|
240
|
-
if (result.errors.length) process.exitCode = 1
|
|
241
|
-
}
|
/package/skill/{vegastack-arch-guardian → arch-guardian}/assets/deployment-review-template.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/agent-product.md
RENAMED
|
File without changes
|
/package/skill/{vegastack-arch-guardian → arch-guardian}/references/architecture/data-memory.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|