@enwithai/product-check 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.
Files changed (102) hide show
  1. package/ATTRIBUTION.md +6 -6
  2. package/CHANGELOG.md +66 -0
  3. package/NOTICE.md +5 -3
  4. package/README.md +34 -19
  5. package/SECURITY.md +5 -1
  6. package/dashboard/assurance.css +71 -0
  7. package/dashboard/assurance.js +21 -0
  8. package/dashboard/dashboard.css +6 -0
  9. package/dist/assurance/adapters.d.ts +32 -0
  10. package/dist/assurance/adapters.js +76 -0
  11. package/dist/assurance/approvals.d.ts +34 -0
  12. package/dist/assurance/approvals.js +133 -0
  13. package/dist/assurance/cli.d.ts +13 -0
  14. package/dist/assurance/cli.js +863 -0
  15. package/dist/assurance/dashboard.d.ts +18 -0
  16. package/dist/assurance/dashboard.js +267 -0
  17. package/dist/assurance/diff.d.ts +20 -0
  18. package/dist/assurance/diff.js +99 -0
  19. package/dist/assurance/evidence-log.d.ts +20 -0
  20. package/dist/assurance/evidence-log.js +100 -0
  21. package/dist/assurance/fix-packets.d.ts +12 -0
  22. package/dist/assurance/fix-packets.js +178 -0
  23. package/dist/assurance/gate.d.ts +37 -0
  24. package/dist/assurance/gate.js +324 -0
  25. package/dist/assurance/lanes.d.ts +61 -0
  26. package/dist/assurance/lanes.js +380 -0
  27. package/dist/assurance/policy.d.ts +10 -0
  28. package/dist/assurance/policy.js +85 -0
  29. package/dist/assurance/proof.d.ts +35 -0
  30. package/dist/assurance/proof.js +146 -0
  31. package/dist/assurance/register.d.ts +41 -0
  32. package/dist/assurance/register.js +480 -0
  33. package/dist/assurance/render-md.d.ts +17 -0
  34. package/dist/assurance/render-md.js +212 -0
  35. package/dist/assurance/repo-state.d.ts +15 -0
  36. package/dist/assurance/repo-state.js +79 -0
  37. package/dist/assurance/review.d.ts +52 -0
  38. package/dist/assurance/review.js +408 -0
  39. package/dist/assurance/risk.d.ts +21 -0
  40. package/dist/assurance/risk.js +184 -0
  41. package/dist/assurance/types.d.ts +573 -0
  42. package/dist/assurance/types.js +6 -0
  43. package/dist/assurance/util.d.ts +24 -0
  44. package/dist/assurance/util.js +89 -0
  45. package/dist/assurance/verification.d.ts +30 -0
  46. package/dist/assurance/verification.js +212 -0
  47. package/dist/assurance/verify.d.ts +26 -0
  48. package/dist/assurance/verify.js +304 -0
  49. package/dist/blueprint/build.d.ts +3 -1
  50. package/dist/blueprint/build.js +41 -5
  51. package/dist/cli/run.js +103 -5
  52. package/dist/contract/contract.d.ts +4 -0
  53. package/dist/contract/contract.js +16 -0
  54. package/dist/dashboard/render.d.ts +5 -0
  55. package/dist/dashboard/render.js +107 -3
  56. package/dist/discovery/pops.d.ts +2 -0
  57. package/dist/discovery/pops.js +7 -5
  58. package/dist/exec/process.d.ts +27 -0
  59. package/dist/exec/process.js +175 -0
  60. package/dist/importers/operational.d.ts +56 -0
  61. package/dist/importers/operational.js +217 -0
  62. package/dist/importers/reports.d.ts +24 -0
  63. package/dist/importers/reports.js +37 -1
  64. package/dist/inspectors/source/surface.d.ts +30 -0
  65. package/dist/inspectors/source/surface.js +529 -0
  66. package/dist/parse/yaml.d.ts +4 -0
  67. package/dist/parse/yaml.js +39 -0
  68. package/dist/reporting/markdown.d.ts +1 -0
  69. package/dist/reporting/markdown.js +18 -1
  70. package/dist/reporting/spec-markdown.d.ts +9 -0
  71. package/dist/reporting/spec-markdown.js +144 -0
  72. package/dist/run.d.ts +2 -0
  73. package/dist/run.js +52 -2
  74. package/dist/schema/registry.js +10 -0
  75. package/dist/scoring/score.js +1 -1
  76. package/dist/spec/build.d.ts +28 -0
  77. package/dist/spec/build.js +278 -0
  78. package/dist/types.d.ts +78 -2
  79. package/dist/types.js +6 -1
  80. package/dist/validation/catalog.d.ts +1 -1
  81. package/dist/validation/catalog.js +125 -2
  82. package/dist/validation/context.d.ts +6 -1
  83. package/dist/validation/import-evaluators.js +37 -3
  84. package/dist/validation/integrity.js +4 -0
  85. package/dist/validation/operational-evaluators.d.ts +3 -0
  86. package/dist/validation/operational-evaluators.js +153 -0
  87. package/dist/validation/source-evaluators.js +1 -1
  88. package/package.json +4 -1
  89. package/schemas/approval.schema.json +98 -0
  90. package/schemas/as-built-spec.schema.json +356 -0
  91. package/schemas/assurance-common.schema.json +291 -0
  92. package/schemas/assurance-policy.schema.json +185 -0
  93. package/schemas/blueprint.schema.json +3 -0
  94. package/schemas/change-review.schema.json +711 -0
  95. package/schemas/closure-register.schema.json +499 -0
  96. package/schemas/evidence-record.schema.json +94 -0
  97. package/schemas/fix-packet.schema.json +272 -0
  98. package/schemas/release-gate.schema.json +308 -0
  99. package/schemas/reviewer-findings.schema.json +49 -0
  100. package/schemas/scan-contract.schema.json +20 -0
  101. package/schemas/scorecard.schema.json +1 -1
  102. package/schemas/verification-run.schema.json +199 -0
package/ATTRIBUTION.md CHANGED
@@ -15,8 +15,8 @@
15
15
  ## Development provenance
16
16
 
17
17
  Product Check was written for this repository with substantial AI assistance (Claude Code), directed and reviewed by
18
- the project's human sponsor. Commits carry a `Co-Authored-By` trailer. It has **no runtime dependencies** and contains
19
- no vendored third-party code. Test fixtures and the demo application are synthetic; credential-like strings in them
18
+ the project's human sponsor. Commits carry a `Co-Authored-By` trailer. It has **one runtime dependency**, `yaml` (ISC,
19
+ by Eemeli Aro), and contains no vendored third-party code. Test fixtures and the demo application are synthetic; credential-like strings in them
20
20
  are generated at run time and are not real.
21
21
 
22
22
  ## Built on
@@ -33,10 +33,10 @@ references are in [REFERENCES.md](REFERENCES.md). Listing a project does not mea
33
33
 
34
34
  ## Frameworks
35
35
 
36
- | Framework | Owner | Relationship | Note |
37
- | ---------------- | ---------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
- | CLEAR (CLEAR CT) | NetworkGain Consulting | `inspired` | The design of the assessment model — separate Customer, Technology and Trust pillars, CT-T0…T6 lifecycle tiers, points of presence, evidence statuses, the rule that strong pillars never hide weak ones, and hard gates overriding scores — was inspired by NetworkGain's CLEAR CT method (v3.1, internal and unpublished). No text, catalog, prompt or scoring table from it is reproduced, and there is no structural relationship. Wording approved for publication on 2026-10-12. |
39
- | TRACE | EnWithAI Technologies | `not-assessed` | Each finding carries an intent → evidence → remediation → retest chain, which may match TRACE's intent; alignment cannot be stated until the TRACE specification is available. |
36
+ | Framework | Owner | Relationship | Note |
37
+ | ---------------- | ---------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
+ | CLEAR (CLEAR CT) | NetworkGain Consulting | `inspired` | The design of the assessment model — separate Customer, Technology and Trust pillars, CT-T0…T6 lifecycle tiers, points of presence, evidence statuses, the rule that strong pillars never hide weak ones, and hard gates overriding scores — was inspired by NetworkGain's CLEAR CT method (v3.1, internal and unpublished). No text, catalog, prompt or scoring table from it is reproduced, and there is no structural relationship. Wording approved for publication on 2026-10-12. The v3.2 layer added in 0.3.0 (execution phases, finding verification states, closure state machine, release conditions) was likewise inspired by CLEAR CT v3.2 (internal and unpublished); that wording was approved for publication on 2026-11-09. |
39
+ | TRACE | EnWithAI Technologies | `not-assessed` | Each finding carries an intent → evidence → remediation → retest chain, which may match TRACE's intent; alignment cannot be stated until the TRACE specification is available. |
40
40
 
41
41
  ## Ecosystem
42
42
 
package/CHANGELOG.md CHANGED
@@ -6,6 +6,72 @@ versions may break the results schema, which carries its own `schema_version`, a
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.3.0 — 2026-11-09
10
+
11
+ Vibe-code review and verified closure, inspired by CLEAR CT v3.2 (relationship `inspired`; wording approved for publication 2026-11-09). Additive: the results schema stays `0.1.0`, the scan
12
+ pipeline and its outputs are unchanged (the committed demo outputs are byte-identical), and new artifacts carry their
13
+ own assurance schema `0.1.0`. Design: [docs/vibe-code-assurance.md](docs/vibe-code-assurance.md); status:
14
+ [IMPLEMENTATION_AUDIT_v3.2.md](IMPLEMENTATION_AUDIT_v3.2.md).
15
+
16
+ - New commands: `proof` (VC-P1 author-side proof), `review` (PR_DIFF / PRE_PR_LOCAL layered review), `register`,
17
+ `transition`, `fix`, `verify` (VC-P5 at the exact fix commit), `keygen`, `approve` (Ed25519-signed), `close`,
18
+ `accept-risk`, `reject`, `learn`, `fix-packets`, `gate`, `assure`; `blueprint`, `dashboard` and `gate` accept
19
+ `--from <results.json>`.
20
+ - **Safety-model change (opt-in):** `src/exec/process.ts` is the only module that starts processes: operator-declared
21
+ commands with `--allow-exec` (argv, no shell, reduced environment, timeout, output cap, digests) and hardened
22
+ read-only git. The scan pipeline still never executes anything; tests and ESLint enforce the boundary.
23
+ - Six deterministic review lanes, independent verification of findings and external reviewer claims
24
+ (`--reviewer-findings`), de-duplication, repeat detection and a review-cycle limit with escalation.
25
+ - Risk routing by impacted surface with protected paths, domain owners and an opt-in LOW-risk waiver; approvals bind
26
+ to the reviewed commit (STALE after a new commit); bots, unsigned names and implementers never count.
27
+ - Closure register with hash-chained transitions and invariant checks on load; signed risk acceptance with expiry;
28
+ false positives with secondary support for P0/P1; REGRESSED with history; regression corpus.
29
+ - Fix packets, `PRODUCT_CHECK_REVIEW.md`, `PRODUCT_CHECK_BLUEPRINT.md` (Mermaid as-is/to-be),
30
+ `PRODUCT_CHECK_RELEASE_GATE.md`, `release-gate.json` (GO / CONDITIONAL_GO / NO_GO / UNKNOWN, VC-P0…P7 progress),
31
+ append-only `review-evidence.jsonl`, and `dashboard/index.html` rendered from those files.
32
+ - Optional versioned adapters: Impact Check 2.0, Spec Check 1.0, Outcome Check 1.0.
33
+ - Ten new JSON Schemas (`assurance-common`, `assurance-policy`, `evidence-record`, `change-review`, `approval`,
34
+ `verification-run`, `fix-packet`, `closure-register`, `release-gate`, `reviewer-findings`); `validate` checks them.
35
+ - `examples/closure-demo/`: a scripted end-to-end run on a synthetic app.
36
+
37
+ ## 0.2.0 — 2026-10-28
38
+
39
+ Commons integration and the first CT-T4 controls. Rubric `pc-rubric-0.2` (51 controls; scoring rules unchanged);
40
+ results schema `0.1.0`, additive. Results and baselines written by 0.1.0 still validate and load.
41
+
42
+ - CT-T4 operational imports ([docs/operational-imports.md](docs/operational-imports.md)): `--openslo` (OpenSLO v1
43
+ YAML/JSON, repeatable), `--alert-rules` (Prometheus/Mimir rule files, `PrometheusRule`, `/api/v1/rules` JSON,
44
+ repeatable) and `--service-graph` (Jaeger `/api/dependencies`, `traces_service_graph_request_total` query result).
45
+ New controls PC-T-18 (SLOs complete), PC-T-19 (every SLO alerted on), PC-T-20 (declared dependencies observed at
46
+ runtime, with `--architecture-check`); PC-T-14 is now evaluated from these files; the observability PoP is
47
+ AVAILABLE when they are given.
48
+ - First runtime dependency: `yaml` 2.9.1 (ISC, pinned), used only through `src/parse/yaml.ts` (core schema, unique
49
+ keys, at most 100 aliases, 10 MiB per file).
50
+ - `--architecture-check <report.json>` (contract `imports.architecture_check`) imports an Architecture Check report
51
+ (schema 1.0 or 1.1, `analyze`/`check` output). New control **PC-T-17 "Architecture model matches the code"**
52
+ (technology, CT-T3, weight 1, P2): PASS with no verified drift of medium severity or above, PARTIAL with one
53
+ `architecture-drift` finding otherwise, NOT_TESTED without a report. Hypotheses and unknowns never count. Rules in
54
+ `docs/interoperability.md`.
55
+ - Rubric `pc-rubric-0.2` (48 controls; scoring rules unchanged). The scorecard schema accepts `pc-rubric-0.1` and
56
+ `pc-rubric-0.2`, and integrity recomputation keeps the stored version, so 0.1.0 results still validate and load as
57
+ retest baselines. Results schema stays `0.1.0`; `imports.architecture_check` is optional in the contract schema.
58
+ - `product-check spec`: reverse-engineers an as-built specification from a repository (pages, endpoints, data
59
+ entities, integrations, configuration names, auth boundaries, test titles) with evidence for every item. It writes
60
+ `as-built-spec.json` (new schema `as-built-spec.schema.json`) and a Spec Kit `specs/000-as-built/spec.md` that Spec
61
+ Check can lint. Intent that code cannot show is left as `[NEEDS CLARIFICATION]`. Source only; nothing executed.
62
+ - `run`/`retest` derive the as-built spec from the same snapshot. The blueprint embeds it as the new optional field
63
+ `as_is.as_built`, and `as-built-spec.json` plus `specs/000-as-built/spec.md` are written next to the report
64
+ (`blueprint --out-dir` re-renders them). Traceability now names the as-built requirement a finding touches
65
+ (`As-built FR-002; requirement PC-…`): pages, endpoints and entities match by file, integrations and configuration
66
+ only by exact line, and application URLs by path. Scores, verdicts and findings are unchanged.
67
+ - Schema: additive, still `0.1.0`. Existing contracts, observations and baselines remain valid. Results that carry
68
+ `as_is.as_built` do not validate with the schemas bundled in 0.1.0 and earlier.
69
+ - Dashboard: a sixth view, **As-built spec**, shows the discovered surface, the questions only a person can answer,
70
+ user stories, and every FR/NFR/SC/EC item with its certainty, source, evidence and linked findings (open or fixed),
71
+ with type, certainty and text filters. It is rendered from the results JSON like every other view.
72
+ - Demo: the baseline, retest and repository-only examples include the as-built spec, and
73
+ `examples/vibe-app-demo/output/as-built/` holds the standalone `spec` output. Both are checked against a fresh run.
74
+
9
75
  ## 0.1.0 — 2026-10-12
10
76
 
11
77
  First release without a pre-release suffix, so `npm install @enwithai/product-check` and `npx @enwithai/product-check` work without a
package/NOTICE.md CHANGED
@@ -8,9 +8,11 @@ license.
8
8
 
9
9
  ## Third-party material
10
10
 
11
- Product Check has **no runtime dependencies** and contains no vendored source code. It reads formats defined by others
12
- (JSON Schema, JUnit XML, HAR, `npm audit` JSON, and the report schemas of Spec Check, Impact Check and Outcome Check);
13
- no code from those projects is included.
11
+ Product Check has **one runtime dependency**, [`yaml`](https://github.com/eemeli/yaml) 2.9.1 (ISC License, Copyright
12
+ Eemeli Aro), installed from npm and not vendored. It reads formats defined by others (JSON Schema, JUnit XML, HAR,
13
+ `npm audit` JSON, the report schemas of Spec Check, Impact Check, Outcome Check and Architecture Check, OpenSLO,
14
+ Prometheus rule files, Jaeger dependencies and the OpenTelemetry service-graph metrics); no code from those projects
15
+ is included.
14
16
 
15
17
  ## Trademarks
16
18
 
package/README.md CHANGED
@@ -12,23 +12,30 @@ Customer, Technology and Trust controls, and gives you:
12
12
  and that states exactly what it does and does not cover;
13
13
  - an **engineering blueprint**: prioritized fixes, acceptance criteria, regression-test candidates and one
14
14
  **work packet** per fix that you (or any coding agent) can act on;
15
- - a **local dashboard** and a **retest** loop that shows verified fixes, regressions and trends.
15
+ - a **local dashboard** and a **retest** loop that shows verified fixes, regressions and trends;
16
+ - **vibe-code review and verified closure** (new in 0.3.0; design inspired by CLEAR CT v3.2): layered change review with independent
17
+ verification of findings, risk-routed signed human approvals, agent-ready fix packets, a closure state machine in
18
+ which a finding is only `VERIFIED_FIXED` after executed proof at the exact fix commit and only `CLOSED` with
19
+ accountable human sign-off, and an explicit `GO / CONDITIONAL_GO / NO_GO / UNKNOWN` release gate
20
+ ([docs/vibe-code-assurance.md](docs/vibe-code-assurance.md)).
16
21
 
17
22
  Everything is written to one versioned JSON file. The report, blueprint and dashboard are renderings of it.
18
23
 
19
- > Status: **early release** (`0.1.0`, pre-1.0, results schema `0.1.0`). Install with
24
+ > Status: **early release** (`0.3.0`, pre-1.0, results schema `0.1.0`, rubric `pc-rubric-0.2`). Install with
20
25
  > `npm install --global @enwithai/product-check`, or build from source as in the demo below.
21
26
 
22
27
  ## What it will not pretend
23
28
 
24
29
  Untested is never passed. Product Check reports `BLOCKED` or `NOT_TESTED` (with the reason) for anything it could not
25
30
  check, lowers coverage accordingly, and withholds a score when evidence is thin. It is **not** a penetration test, a
26
- compliance certification or a guarantee of production readiness. It never runs your repository's code, never logs in,
27
- never submits forms and never sends anything anywhere except passive GET requests to an origin you authorize.
31
+ compliance certification or a guarantee of production readiness. The scan never runs your repository's code, never
32
+ logs in, never submits forms and never sends anything anywhere except passive GET requests to an origin you authorize.
33
+ Only the opt-in `proof` and `verify` commands run commands, and only those you declare in a policy, with
34
+ `--allow-exec` on that invocation.
28
35
 
29
36
  ## Two-minute demo
30
37
 
31
- Requires Node.js 22, 24 or ≥ 26, and git. No account, no network, no runtime dependencies.
38
+ Requires Node.js 22, 24 or ≥ 26, and git. No account, no network, one runtime dependency (`yaml`, for OpenSLO and alert-rule files).
32
39
 
33
40
  ```bash
34
41
  git clone https://github.com/enwithai/product-check.git && cd product-check
@@ -64,7 +71,7 @@ Verdict: READY_WITH_CONDITIONS at CT-T2 ("CONDITIONAL")
64
71
  Customer 100/100 (coverage 92%, confidence HIGH)
65
72
  Technology 94/100 (coverage 100%, confidence MEDIUM)
66
73
  Trust 97/100 (coverage 83%, confidence HIGH)
67
- Retest vs PCR-20261014T100000Z-7386B1: 35 verified fixed, 0 new, 0 reopened, 0 regressed control(s)
74
+ Retest vs PCR-20261014T100000Z-D74DF2: 35 verified fixed, 0 new, 0 reopened, 0 regressed control(s)
68
75
  ```
69
76
 
70
77
  The two remaining P2s (no error monitoring, no AI evaluation suite) are real gaps in the fixed demo app. A
@@ -94,8 +101,9 @@ npm audit --json > npm-audit.json
94
101
  product-check run --repo . --test-results junit.xml --npm-audit npm-audit.json --stage PRE_LAUNCH
95
102
  ```
96
103
 
97
- Results go to `.product-check/`: `product-check-results.json`, `report.md`, `blueprint.md`, `work-packets/*.md` and
98
- `dashboard.html` (open it directly; no server). After changing things, run
104
+ Results go to `.product-check/`: `product-check-results.json`, `report.md`, `blueprint.md`, `work-packets/*.md`,
105
+ `dashboard.html` (open it directly; no server) and, when the repository was read, an as-built spec
106
+ (`specs/000-as-built/spec.md`, `as-built-spec.json`) reverse-engineered from its structure. After changing things, run
99
107
  `product-check retest --baseline .product-check/product-check-results.json …` with the same inputs.
100
108
 
101
109
  No deployed URL you can scan? Export a HAR file from your browser's DevTools on your app and pass `--har app.har`.
@@ -107,15 +115,20 @@ authorization or tenant-isolation tests) to the controls Product Check cannot te
107
115
 
108
116
  ## Commands
109
117
 
110
- | Command | What it does |
111
- | ---------------------------------- | ------------------------------------------------------------------------------------------------- |
112
- | `run` | Scan → Test → Validate → Blueprint; writes results and renderings |
113
- | `retest --baseline <file>` | `run` plus comparison: verified fixes, reopened and new findings, regressions, trends |
114
- | `scan` | Discovery only: which points of presence exist and what could be checked (no requests, no checks) |
115
- | `init` | Writes an editable scan contract |
116
- | `validate <file>…` | Schema validation; for results also referential integrity and recomputation of scores and verdict |
117
- | `report`, `blueprint`, `dashboard` | Re-render from a results file |
118
- | `controls`, `schema [name]` | The control catalog and the JSON Schemas |
118
+ | Command | What it does |
119
+ | ----------------------------------- | ------------------------------------------------------------------------------------------------- |
120
+ | `run` | Scan → Test → Validate → Blueprint; writes results and renderings |
121
+ | `retest --baseline <file>` | `run` plus comparison: verified fixes, reopened and new findings, regressions, trends |
122
+ | `scan` | Discovery only: which points of presence exist and what could be checked (no requests, no checks) |
123
+ | `spec` | Reverse-engineers an as-built spec (Spec Kit `spec.md` + JSON) from the repository's structure |
124
+ | `init` | Writes an editable scan contract |
125
+ | `validate <file>…` | Schema validation; for results also referential integrity and recomputation of scores and verdict |
126
+ | `report`, `blueprint`, `dashboard` | Re-render from a results file |
127
+ | `controls`, `schema [name]` | The control catalog and the JSON Schemas |
128
+ | `register`, `review`, `proof` | Closure register; layered change review (PR diff); author-side proof (runs declared checks) |
129
+ | `fix`, `verify`, `approve`, `close` | Record a fix; verify it at the exact commit; signed human approval; verified closure |
130
+ | `accept-risk`, `reject`, `learn` | Accepted risk with expiry; false positive; regression corpus |
131
+ | `fix-packets`, `gate`, `assure` | Agent-ready fix packets; release gate; all assurance reports and `dashboard/index.html` |
119
132
 
120
133
  Exit codes: `0` ok · `1` internal error · `2` usage · `3` input · `4` safety refusal · `5` invalid artifact ·
121
134
  `6` `--fail-on` matched (for CI gating, for example `--fail-on NOT_READY`). Full reference: [docs/cli.md](docs/cli.md).
@@ -134,7 +147,8 @@ Exit codes: `0` ok · `1` internal error · `2` usage · `3` input · `4` safety
134
147
 
135
148
  ## Privacy and security
136
149
 
137
- No telemetry, account, model call or child process. The repository is read, never executed; git-ignored files (such
150
+ No telemetry, account or model call. The scan starts no process: the repository is read, never executed (the opt-in
151
+ `proof`/`verify` commands are the exception, see [vibe-code assurance](docs/vibe-code-assurance.md#execution-model-a-deliberate-change-opt-in-only)); git-ignored files (such
138
152
  as your local `.env`) are listed but never read. Evidence keeps short redacted excerpts, never file or response
139
153
  contents; response headers are filtered to a security-relevant allowlist with cookie values removed. The dashboard is
140
154
  a single file with a hash-pinned script and a `default-src 'none'` Content-Security-Policy. See
@@ -145,7 +159,8 @@ a single file with a hash-pinned script and a `default-src 'none'` Content-Secur
145
159
  [Philosophy](docs/philosophy.md) · [Architecture](docs/architecture.md) ·
146
160
  [Assessment model](docs/assessment-model.md) · [Scoring and gates](docs/scoring-and-gates.md) ·
147
161
  [Lifecycle tiers](docs/lifecycle-tiers.md) · [Safety and authorization](docs/safety-and-authorization.md) ·
148
- [Interoperability](docs/interoperability.md) · [CLI](docs/cli.md) · [Threat model](docs/threat-model.md) ·
162
+ [Interoperability](docs/interoperability.md) · [Vibe-code assurance](docs/vibe-code-assurance.md) ·
163
+ [v3.2 implementation audit](IMPLEMENTATION_AUDIT_v3.2.md) · [CLI](docs/cli.md) · [Threat model](docs/threat-model.md) ·
149
164
  [Branding and attribution](docs/branding-and-attribution.md) · [Implementation status](IMPLEMENTATION_STATUS.md) ·
150
165
  [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Governance](GOVERNANCE.md)
151
166
 
package/SECURITY.md CHANGED
@@ -12,7 +12,11 @@ minimal **synthetic** reproduction. Never send real results, HAR files or reposi
12
12
 
13
13
  ## Security properties Product Check is designed to keep
14
14
 
15
- - It never executes repository code, starts processes or uses `eval`/dynamic imports.
15
+ - The scan (`run`, `retest`, `scan`, `spec`) never executes repository code or starts processes, and nothing uses
16
+ `eval`/dynamic imports. Only `proof` and `verify` (CT3 v3.2) can run commands: those the operator declares in a
17
+ policy, fix record or regression corpus, as argv without a shell, and only with `--allow-exec` on that invocation;
18
+ they then run with your permissions (see docs/safety-and-authorization.md). Read-only git plumbing runs with hooks,
19
+ fsmonitor, external diff/textconv drivers, pagers and system config disabled.
16
20
  - It never reads git-ignored files, and stores only short redacted excerpts and checksums as evidence.
17
21
  - It sends network requests only when explicitly authorized, only passive GETs to fixed paths on one origin, with no
18
22
  cookies, credentials or bodies, a request budget and timeouts. It never follows redirects to other origins.
@@ -0,0 +1,71 @@
1
+ /* SPDX-License-Identifier: MIT */
2
+ /* SPDX-FileCopyrightText: 2026 EnWithAI Technologies */
3
+ /* Assurance dashboard additions (closure funnel, phase strip, decision hero). Inlined after dashboard.css. */
4
+ .funnel {
5
+ display: grid;
6
+ gap: 6px;
7
+ }
8
+ .funnel .step {
9
+ display: grid;
10
+ grid-template-columns: minmax(110px, 180px) 1fr 48px;
11
+ gap: 8px;
12
+ align-items: center;
13
+ }
14
+ .funnel .bar {
15
+ height: 18px;
16
+ border-radius: 4px;
17
+ background: var(--grid);
18
+ overflow: hidden;
19
+ }
20
+ .funnel .bar > span {
21
+ display: block;
22
+ height: 100%;
23
+ background: var(--s-customer);
24
+ }
25
+ .funnel .step.done .bar > span {
26
+ background: var(--good);
27
+ }
28
+ .funnel .step.side .bar > span {
29
+ background: var(--unknown);
30
+ }
31
+ .phases {
32
+ display: grid;
33
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
34
+ gap: 8px;
35
+ }
36
+ .phases .card h4 {
37
+ margin: 0 0 4px;
38
+ font-size: 0.9rem;
39
+ }
40
+ .decision {
41
+ font-size: 2.2rem;
42
+ font-weight: 700;
43
+ line-height: 1.1;
44
+ }
45
+ .insufficient {
46
+ border: 2px dashed var(--unknown);
47
+ padding: 8px 12px;
48
+ border-radius: 8px;
49
+ margin: 8px 0;
50
+ }
51
+ pre.export {
52
+ max-height: 360px;
53
+ overflow: auto;
54
+ white-space: pre;
55
+ }
56
+ button.copy {
57
+ margin: 4px 0;
58
+ }
59
+ .decision.tone-good {
60
+ color: var(--good);
61
+ }
62
+ .decision.tone-warning {
63
+ color: var(--ink);
64
+ text-decoration: underline wavy var(--warning);
65
+ }
66
+ .decision.tone-critical {
67
+ color: var(--critical);
68
+ }
69
+ .decision.tone-unknown {
70
+ color: var(--unknown);
71
+ }
@@ -0,0 +1,21 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // SPDX-FileCopyrightText: 2026 EnWithAI Technologies
3
+ // Progressive enhancement for the assurance dashboard: copy buttons for the agent export and Mermaid sources.
4
+ // Every figure is server-rendered from the JSON artifacts; without JavaScript everything is still readable.
5
+ (function () {
6
+ Array.prototype.slice.call(document.querySelectorAll("[data-copy]")).forEach(function (btn) {
7
+ var target = document.getElementById(btn.getAttribute("data-copy"));
8
+ if (!target || !window.navigator || !window.navigator.clipboard) return;
9
+ btn.hidden = false;
10
+ btn.addEventListener("click", function () {
11
+ window.navigator.clipboard.writeText(target.textContent || "").then(
12
+ function () {
13
+ btn.textContent = "Copied";
14
+ },
15
+ function () {
16
+ btn.textContent = "Copy failed";
17
+ }
18
+ );
19
+ });
20
+ });
21
+ })();
@@ -331,6 +331,12 @@ th {
331
331
  font-weight: 600;
332
332
  background: var(--surface);
333
333
  }
334
+ td.id {
335
+ white-space: nowrap;
336
+ }
337
+ td.id code {
338
+ overflow-wrap: normal;
339
+ }
334
340
  td.num,
335
341
  th.num {
336
342
  text-align: right;
@@ -0,0 +1,32 @@
1
+ import type { ChangeReview } from "./types.js";
2
+ /** Supported report versions, by tool. */
3
+ export declare const ADAPTER_VERSIONS: {
4
+ readonly "impact-check": readonly ["2.0"];
5
+ readonly "spec-check": readonly ["1.0"];
6
+ readonly "outcome-check": readonly ["1.0"];
7
+ };
8
+ /** Impact Check 2.0 → risk input. */
9
+ export declare function impactAdapter(data: unknown, source: string): NonNullable<ChangeReview["risk"]["impact_check"]> & {
10
+ modules: string[];
11
+ };
12
+ /** Spec Check 1.0 → requirements and gaps. */
13
+ export interface SpecInput {
14
+ requirements: {
15
+ id: string;
16
+ path: string;
17
+ line: number | null;
18
+ }[];
19
+ gaps: {
20
+ id: string;
21
+ severity: string;
22
+ summary: string;
23
+ path: string | null;
24
+ }[];
25
+ }
26
+ export declare function specAdapter(data: unknown, source: string): SpecInput;
27
+ /** Outcome Check 1.0 → delivery signal. */
28
+ export interface OutcomeInput {
29
+ ci_first_pass_rate: number | null;
30
+ revert_rate: number | null;
31
+ }
32
+ export declare function outcomeAdapter(data: unknown, source: string): OutcomeInput;
@@ -0,0 +1,76 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // SPDX-FileCopyrightText: 2026 EnWithAI Technologies
3
+ /**
4
+ * Optional, versioned adapters to the sibling Engineering Commons tools. Product Check reads only documented
5
+ * fields of their JSON reports and never runs or bundles their engines (no dependency in either direction):
6
+ *
7
+ * Impact Check 2.0 → change-risk input (high-severity impact findings raise risk; affected modules)
8
+ * Spec Check 1.0 → requirement IDs and acceptance gaps for the spec/outcome lane and fix packets
9
+ * Outcome Check 1.0 → intent-to-delivery signal for the spec/outcome lane (never a whole-product score)
10
+ *
11
+ * An unsupported version is rejected with a clear message instead of being guessed at.
12
+ */
13
+ import { inputError } from "../errors.js";
14
+ const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
15
+ /** Supported report versions, by tool. */
16
+ export const ADAPTER_VERSIONS = {
17
+ "impact-check": ["2.0"],
18
+ "spec-check": ["1.0"],
19
+ "outcome-check": ["1.0"],
20
+ };
21
+ /** Impact Check 2.0 → risk input. */
22
+ export function impactAdapter(data, source) {
23
+ if (!isObj(data) || data["schema_version"] !== "2.0" || !Array.isArray(data["findings"]))
24
+ throw inputError(`${source} is not an Impact Check report (supported: ${ADAPTER_VERSIONS["impact-check"].join(", ")}).`, "Produce it with `impact-check --format json`.");
25
+ const findings = data["findings"].filter(isObj);
26
+ const modules = Array.isArray(data["affected_modules"])
27
+ ? data["affected_modules"]
28
+ .filter(isObj)
29
+ .map((m) => String(m["path"] ?? ""))
30
+ .filter(Boolean)
31
+ : [];
32
+ const rev = isObj(data["repo_revision"]) ? data["repo_revision"] : {};
33
+ return {
34
+ schema_version: "2.0",
35
+ high: findings.filter((f) => f["severity"] === "high" && f["status"] !== "unknown").length,
36
+ affected_modules: modules.length,
37
+ commit: typeof rev["commit"] === "string" ? rev["commit"] : null,
38
+ modules,
39
+ };
40
+ }
41
+ export function specAdapter(data, source) {
42
+ if (!isObj(data) ||
43
+ data["schema_version"] !== "1.0" ||
44
+ !Array.isArray(data["requirements"]) ||
45
+ !Array.isArray(data["findings"]))
46
+ throw inputError(`${source} is not a Spec Check report (supported: ${ADAPTER_VERSIONS["spec-check"].join(", ")}).`, "Produce it with `spec-check check --format json`.");
47
+ return {
48
+ requirements: data["requirements"].filter(isObj).map((r) => ({
49
+ id: String(r["id"] ?? ""),
50
+ path: String(r["path"] ?? ""),
51
+ line: typeof r["line_start"] === "number" ? r["line_start"] : null,
52
+ })),
53
+ gaps: data["findings"]
54
+ .filter(isObj)
55
+ .filter((f) => f["severity"] === "high" || f["severity"] === "medium")
56
+ .map((f) => {
57
+ const ev = Array.isArray(f["evidence"]) ? f["evidence"].filter(isObj)[0] : undefined;
58
+ return {
59
+ id: String(f["id"] ?? ""),
60
+ severity: String(f["severity"]),
61
+ summary: String(f["summary"] ?? ""),
62
+ path: ev && typeof ev["path"] === "string" ? ev["path"] : null,
63
+ };
64
+ }),
65
+ };
66
+ }
67
+ export function outcomeAdapter(data, source) {
68
+ if (!isObj(data) || data["schema_version"] !== "1.0" || !Array.isArray(data["metrics"]))
69
+ throw inputError(`${source} is not an Outcome Check report (supported: ${ADAPTER_VERSIONS["outcome-check"].join(", ")}).`, "Produce it with `outcome-check report --format json`.");
70
+ const val = (id) => {
71
+ const m = data["metrics"].find((x) => isObj(x) && x["id"] === id);
72
+ const all = isObj(m) && isObj(m["segments"]) ? m["segments"]["all"] : null;
73
+ return isObj(all) && typeof all["value"] === "number" ? all["value"] : null;
74
+ };
75
+ return { ci_first_pass_rate: val("ci_first_pass_rate"), revert_rate: val("revert_rate") };
76
+ }
@@ -0,0 +1,34 @@
1
+ import { type Approval, type ApprovalEvaluation, type AssurancePolicy } from "./types.js";
2
+ /** Key ID: first 16 hex chars of SHA-256 over the DER-encoded SPKI public key. */
3
+ export declare function keyId(publicKeyPem: string): string;
4
+ /** Generates an Ed25519 key pair (PEM). */
5
+ export declare function generateApproverKeys(): {
6
+ privateKey: string;
7
+ publicKey: string;
8
+ };
9
+ type Unsigned = Omit<Approval, "approval_id" | "signature">;
10
+ /** Creates and signs an approval with a private key (PEM). */
11
+ export declare function signApproval(input: Omit<Unsigned, "schema_version" | "kind">, privateKeyPem: string): Approval;
12
+ /** Records an unsigned claim (e.g. a name typed with --approver). It never counts as approval. */
13
+ export declare function unsignedClaim(input: Omit<Unsigned, "schema_version" | "kind">): Approval;
14
+ /** Evaluates one approval for a required scope and commit. */
15
+ export declare function evaluateApproval(a: Approval, policy: AssurancePolicy, want: {
16
+ scopeType: Approval["scope"]["type"];
17
+ ref: string;
18
+ sha: string | null;
19
+ excluded: string[];
20
+ }): ApprovalEvaluation;
21
+ /** Evaluates all approvals for a scope; counts distinct valid human approvers and checks domain owners. */
22
+ export declare function tallyApprovals(approvals: Approval[], policy: AssurancePolicy, want: {
23
+ scopeType: Approval["scope"]["type"];
24
+ ref: string;
25
+ sha: string | null;
26
+ excluded: string[];
27
+ }, required: number, owners?: string[]): {
28
+ evaluations: ApprovalEvaluation[];
29
+ validApprovers: string[];
30
+ satisfied: boolean;
31
+ missingOwners: string[];
32
+ stale: number;
33
+ };
34
+ export {};
@@ -0,0 +1,133 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // SPDX-FileCopyrightText: 2026 EnWithAI Technologies
3
+ /**
4
+ * Accountable human approvals. An approval is a signed statement (Ed25519 over canonical JSON) by an approver the
5
+ * policy lists with a public key. A name passed on the command line proves nothing: only a signature that verifies
6
+ * against a policy key counts. Bots can sign records but never fill a human approval slot; an approval is bound
7
+ * to the exact commit it reviewed and becomes STALE when the commit changes; the implementer of a fix cannot
8
+ * approve its closure.
9
+ *
10
+ * Limit: Product Check proves possession of a registered key, not that a person pressed the key. Key custody is the
11
+ * operator's control (see docs/vibe-code-assurance.md).
12
+ */
13
+ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify, } from "node:crypto";
14
+ import { usageError } from "../errors.js";
15
+ import { stableId } from "../util/hash.js";
16
+ import { ASSURANCE_SCHEMA_VERSION, } from "./types.js";
17
+ import { canonicalJson } from "./util.js";
18
+ /** Key ID: first 16 hex chars of SHA-256 over the DER-encoded SPKI public key. */
19
+ export function keyId(publicKeyPem) {
20
+ const der = createPublicKey(publicKeyPem).export({ type: "spki", format: "der" });
21
+ return createHash("sha256").update(der).digest("hex").slice(0, 16);
22
+ }
23
+ /** Generates an Ed25519 key pair (PEM). */
24
+ export function generateApproverKeys() {
25
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519");
26
+ return {
27
+ privateKey: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
28
+ publicKey: publicKey.export({ type: "spki", format: "pem" }).toString(),
29
+ };
30
+ }
31
+ const payloadOf = (a) => canonicalJson({
32
+ schema_version: a.schema_version,
33
+ kind: a.kind,
34
+ approver: a.approver,
35
+ scope: a.scope,
36
+ reviewed_sha: a.reviewed_sha,
37
+ decision: a.decision,
38
+ comment: a.comment,
39
+ created_at: a.created_at,
40
+ });
41
+ /** Creates and signs an approval with a private key (PEM). */
42
+ export function signApproval(input, privateKeyPem) {
43
+ let key;
44
+ try {
45
+ key = createPrivateKey(privateKeyPem);
46
+ }
47
+ catch {
48
+ throw usageError("The approver key is not a readable private key (PKCS#8 PEM).");
49
+ }
50
+ if (key.asymmetricKeyType !== "ed25519")
51
+ throw usageError("Approver keys must be Ed25519.");
52
+ const unsigned = { schema_version: ASSURANCE_SCHEMA_VERSION, kind: "approval", ...input };
53
+ const payload = payloadOf(unsigned);
54
+ const value = sign(null, Buffer.from(payload), key).toString("base64");
55
+ const pub = createPublicKey(key).export({ type: "spki", format: "pem" }).toString();
56
+ return {
57
+ ...unsigned,
58
+ approval_id: stableId("AP", [payload], 12),
59
+ signature: { algorithm: "ed25519", key_id: keyId(pub), value },
60
+ };
61
+ }
62
+ /** Records an unsigned claim (e.g. a name typed with --approver). It never counts as approval. */
63
+ export function unsignedClaim(input) {
64
+ const unsigned = { schema_version: ASSURANCE_SCHEMA_VERSION, kind: "approval", ...input };
65
+ return { ...unsigned, approval_id: stableId("AP", [payloadOf(unsigned), "unsigned"], 12), signature: null };
66
+ }
67
+ /** Verifies the approval's signature against the policy; returns null when valid, else the failing status. */
68
+ function signatureStatus(a, policy) {
69
+ const approver = policy.approvers.find((p) => p.id === a.approver.id);
70
+ if (!approver)
71
+ return "UNKNOWN_APPROVER";
72
+ if (!a.signature)
73
+ return "UNSIGNED";
74
+ if (a.signature.key_id !== keyId(approver.public_key))
75
+ return "INVALID_SIGNATURE";
76
+ try {
77
+ const ok = verify(null, Buffer.from(payloadOf(a)), createPublicKey(approver.public_key), Buffer.from(a.signature.value, "base64"));
78
+ if (!ok)
79
+ return "INVALID_SIGNATURE";
80
+ }
81
+ catch {
82
+ return "INVALID_SIGNATURE";
83
+ }
84
+ if (a.approval_id !== stableId("AP", [payloadOf(a)], 12))
85
+ return "INVALID_SIGNATURE";
86
+ if (approver.type !== "human" || a.approver.type !== "human")
87
+ return "NOT_HUMAN";
88
+ return null;
89
+ }
90
+ /** Evaluates one approval for a required scope and commit. */
91
+ export function evaluateApproval(a, policy, want) {
92
+ const res = (status, reason) => ({
93
+ approval_id: a.approval_id,
94
+ approver_id: a.approver.id,
95
+ status,
96
+ reason,
97
+ });
98
+ const sig = signatureStatus(a, policy);
99
+ if (sig === "UNKNOWN_APPROVER")
100
+ return res(sig, `${a.approver.id} is not an approver in the policy.`);
101
+ if (sig === "UNSIGNED")
102
+ return res(sig, "Unsigned claim: a typed name is not proof of approval.");
103
+ if (sig === "INVALID_SIGNATURE")
104
+ return res(sig, "The signature does not verify against the approver's policy key (edited or forged).");
105
+ if (sig === "NOT_HUMAN")
106
+ return res(sig, `${a.approver.id} is a bot; automated reviewers cannot fill a human approval slot.`);
107
+ if (a.scope.type !== want.scopeType || a.scope.ref !== want.ref)
108
+ return res("WRONG_SCOPE", `Approval covers ${a.scope.type}:${a.scope.ref}, not ${want.scopeType}:${want.ref}.`);
109
+ if (want.excluded.includes(a.approver.id))
110
+ return res("SELF_APPROVAL", `${a.approver.id} implemented this change and cannot approve it.`);
111
+ if (a.decision !== "approve")
112
+ return res("REJECTED", `${a.approver.id} rejected: ${a.comment || "(no comment)"}`);
113
+ if (want.sha !== null && a.reviewed_sha !== want.sha)
114
+ return res("STALE", `Reviewed ${a.reviewed_sha.slice(0, 12)}, but the current commit is ${want.sha.slice(0, 12)}; re-approval required.`);
115
+ return res("VALID", `Signed by ${a.approver.id} for ${a.reviewed_sha.slice(0, 12)}.`);
116
+ }
117
+ /** Evaluates all approvals for a scope; counts distinct valid human approvers and checks domain owners. */
118
+ export function tallyApprovals(approvals, policy, want, required, owners = []) {
119
+ const relevant = approvals.filter((a) => a.scope.type === want.scopeType && a.scope.ref === want.ref);
120
+ const evaluations = relevant.map((a) => evaluateApproval(a, policy, want));
121
+ const rejected = evaluations.some((e) => e.status === "REJECTED");
122
+ const validApprovers = [
123
+ ...new Set(evaluations.filter((e) => e.status === "VALID").map((e) => e.approver_id)),
124
+ ].sort();
125
+ const missingOwners = owners.filter((o) => !validApprovers.includes(o));
126
+ return {
127
+ evaluations,
128
+ validApprovers,
129
+ satisfied: !rejected && validApprovers.length >= required && missingOwners.length === 0,
130
+ missingOwners,
131
+ stale: evaluations.filter((e) => e.status === "STALE").length,
132
+ };
133
+ }