@qijenchen/governance 0.1.0-beta.94

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 +89 -0
  2. package/bin/attest.mjs +4 -0
  3. package/bin/check.mjs +4 -0
  4. package/bin/doctor.mjs +4 -0
  5. package/bin/generate.mjs +4 -0
  6. package/bin/governance.mjs +4 -0
  7. package/bin/hook.mjs +4 -0
  8. package/bin/upgrade.mjs +4 -0
  9. package/canonical/gates.json +42 -0
  10. package/canonical/manifest.json +476 -0
  11. package/canonical/plugin-aliases.json +25 -0
  12. package/canonical/provider-lifecycle.json +48 -0
  13. package/canonical/providers.json +455 -0
  14. package/canonical/roles.json +12 -0
  15. package/canonical/rules.json +81 -0
  16. package/canonical/schemas/attestation.schema.json +61 -0
  17. package/canonical/schemas/diagnostic.schema.json +37 -0
  18. package/canonical/schemas/gates.schema.json +27 -0
  19. package/canonical/schemas/lock.schema.json +189 -0
  20. package/canonical/schemas/manifest.schema.json +103 -0
  21. package/canonical/schemas/plugin-aliases.schema.json +59 -0
  22. package/canonical/schemas/provider-hook-coverage.schema.json +173 -0
  23. package/canonical/schemas/provider-lifecycle.schema.json +126 -0
  24. package/canonical/schemas/providers.schema.json +917 -0
  25. package/canonical/schemas/roles.schema.json +27 -0
  26. package/canonical/schemas/rules.schema.json +46 -0
  27. package/canonical/schemas/upgrade-plan.schema.json +31 -0
  28. package/package.json +42 -0
  29. package/src/authority-decision-evidence.mjs +413 -0
  30. package/src/canonical-order.mjs +8 -0
  31. package/src/carrier-projection.mjs +407 -0
  32. package/src/cli.mjs +114 -0
  33. package/src/closed-tool-execution.mjs +1001 -0
  34. package/src/common.mjs +278 -0
  35. package/src/contract.mjs +760 -0
  36. package/src/hook-api.mjs +107 -0
  37. package/src/index.mjs +14 -0
  38. package/src/provider-hook-normalization.mjs +1646 -0
  39. package/src/provider-review-binding.mjs +2377 -0
  40. package/src/snapshot.mjs +520 -0
@@ -0,0 +1,107 @@
1
+ import {
2
+ canonicalize,
3
+ diagnostic,
4
+ humanResult,
5
+ readStdin,
6
+ result,
7
+ stableJson,
8
+ } from './common.mjs'
9
+ import { inspectContract } from './contract.mjs'
10
+ import { normalizeProviderHookInput, providerHookIssueMessage } from './provider-hook-normalization.mjs'
11
+
12
+ export function normalizeProviderEvent(contract, providerId, input) {
13
+ const provider = contract.providers.find((item) => item.id === providerId)
14
+ if (!provider) return canonicalize({ provider: null, event: '', tool: '', operation: 'read', paths: [], blocked: true, evidence: [] })
15
+ return canonicalize(normalizeProviderHookInput({ provider, input, repoRoot: contract.repoRoot }).normalized)
16
+ }
17
+
18
+ export async function evaluateHook({
19
+ provider,
20
+ input,
21
+ repoRoot = process.cwd(),
22
+ manifestPath,
23
+ role,
24
+ outputDirectory,
25
+ lockFile,
26
+ } = {}) {
27
+ const inspected = await inspectContract({ repoRoot, manifestPath, role, outputDirectory, lockFile, verifySources: false })
28
+ const diagnostics = [...inspected.diagnostics]
29
+ if (!inspected.contract || diagnostics.some((item) => ['FAIL', 'BLOCK', 'ERROR'].includes(item.outcome))) return { result: result('hook', diagnostics), exitCode: 2, normalized: null }
30
+ const contract = inspected.contract
31
+ const providerContract = contract.providers.find((item) => item.id === provider)
32
+ if (!providerContract) {
33
+ diagnostics.push(diagnostic({ ruleId: 'GOV-PROVIDER-001', severity: 'critical', kind: 'provider-unknown', path: provider || '', outcome: 'BLOCK', message: 'Hook provider is not registered.' }))
34
+ return { result: result('hook', diagnostics), exitCode: 2, normalized: null }
35
+ }
36
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
37
+ diagnostics.push(diagnostic({ ruleId: 'GOV-PROVIDER-001', severity: 'critical', kind: 'hook-input-invalid', path: provider, outcome: 'BLOCK', message: 'Hook input must be a JSON object.' }))
38
+ return { result: result('hook', diagnostics), exitCode: providerContract.blockingExitCode || 2, normalized: null }
39
+ }
40
+
41
+ const hookInput = normalizeProviderHookInput({ provider: providerContract, input, repoRoot: contract.repoRoot })
42
+ let normalized = canonicalize(hookInput.normalized)
43
+ for (const issue of hookInput.issues) {
44
+ diagnostics.push(diagnostic({
45
+ ruleId: 'GOV-PROVIDER-001',
46
+ severity: 'critical',
47
+ ...issue.evidence,
48
+ outcome: 'BLOCK',
49
+ message: providerHookIssueMessage(issue),
50
+ }))
51
+ }
52
+ if (normalized.operation === 'write') {
53
+ const protectedPaths = [contract.lockFile, contract.outputDirectory]
54
+ const generatedRule = contract.rules.find((rule) => rule.ruleId === 'GOV-GENERATED-001')
55
+ for (const path of normalized.paths) {
56
+ const protectedPath = protectedPaths.find((candidate) => path === candidate || path.startsWith(`${candidate}/`))
57
+ if (!protectedPath) continue
58
+ diagnostics.push(diagnostic({
59
+ ruleId: generatedRule?.ruleId || 'GOV-GENERATED-001',
60
+ severity: generatedRule?.severity || 'critical',
61
+ kind: 'generated-artifact-write',
62
+ path,
63
+ expected: 'edit canonical source then regenerate',
64
+ actual: normalized.tool || normalized.event,
65
+ outcome: 'BLOCK',
66
+ message: 'Direct writes to generated governance artifacts are blocked.',
67
+ }))
68
+ }
69
+ }
70
+
71
+ if (!diagnostics.length) diagnostics.push(diagnostic({ ruleId: 'GOV-GENERATED-001', severity: 'info', kind: 'hook-pass', path: normalized.paths.join(',') || '<none>', outcome: 'PASS', message: 'Normalized provider operation does not write a protected generated artifact.' }))
72
+ const blocked = diagnostics.some((item) => ['BLOCK', 'FAIL', 'ERROR'].includes(item.outcome))
73
+ const ordered = result('hook', diagnostics).diagnostics
74
+ normalized = canonicalize({
75
+ ...normalized,
76
+ blocked,
77
+ evidence: ordered
78
+ .filter((item) => ['BLOCK', 'FAIL', 'ERROR'].includes(item.outcome))
79
+ .map((item) => item.evidence),
80
+ })
81
+ const value = result('hook', diagnostics, { provider, normalized })
82
+ return { result: value, exitCode: blocked ? (providerContract.blockingExitCode || 2) : 0, normalized }
83
+ }
84
+
85
+ export function nativeHookStreams(evaluated) {
86
+ if (evaluated.exitCode === 0) return { stdout: '', stderr: '' }
87
+ return { stdout: '', stderr: `${humanResult(evaluated.result)}\n` }
88
+ }
89
+
90
+ export async function runHookCli({ provider, repoRoot, manifestPath, role, outputDirectory, lockFile, json = false } = {}) {
91
+ let input
92
+ try {
93
+ const raw = await readStdin()
94
+ input = raw.trim() ? JSON.parse(raw) : {}
95
+ } catch (error) {
96
+ input = null
97
+ }
98
+ const evaluated = await evaluateHook({ provider, input, repoRoot, manifestPath, role, outputDirectory, lockFile })
99
+ if (json) process.stdout.write(stableJson(evaluated.result))
100
+ else {
101
+ const streams = nativeHookStreams(evaluated)
102
+ if (streams.stdout) process.stdout.write(streams.stdout)
103
+ if (streams.stderr) process.stderr.write(streams.stderr)
104
+ }
105
+ process.exitCode = evaluated.exitCode
106
+ return evaluated
107
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,14 @@
1
+ export { inspectContract, inspectSkillParity, sourceRecords } from './contract.mjs'
2
+ export { evaluateHook, normalizeProviderEvent, runHookCli } from './hook-api.mjs'
3
+ export { normalizeProviderHookInput, providerHookIssueMessage } from './provider-hook-normalization.mjs'
4
+ export {
5
+ applyUpgrade,
6
+ attestRepository,
7
+ buildExpectedSnapshot,
8
+ checkRepository,
9
+ doctorRepository,
10
+ generateRepository,
11
+ planUpgrade,
12
+ snapshotState,
13
+ } from './snapshot.mjs'
14
+ export { canonicalize, diagnostic, digest, inventoryTree, stableJson, treeDigest } from './common.mjs'