yam-flow 2.8.4 → 2.8.5

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.
@@ -0,0 +1,192 @@
1
+ # Test check completion gate
2
+
3
+ Test check validates changed work before a completion claim, even without a commit
4
+ or push request. It is separate from report formatting and Security check. It does
5
+ not certify every possible input or prove a live deployment from a fixture.
6
+
7
+ ## Contract
8
+
9
+ 1. At work start, identify the exact baseline commit and expected behavior. Review
10
+ the project-owned policy before executing its commands. Commands run as the
11
+ caller without a shell; they are authorized project code, not a security sandbox.
12
+ 2. Configure `testing/policy.json` (shared) or `.yam/testing/policy.json` (private).
13
+ `yam template test-check` prints a starter that cannot pass without an adapter.
14
+ 3. Each changed source file must match a surface's exact path or `directory/**`.
15
+ Declare risk, targets and required scenarios. Built-in risk floors cannot be
16
+ reduced by deleting checks. API/DB/auth/UI require integration or E2E evidence.
17
+ 4. At implementation end, `plan` checks impact and required scenarios; `run` executes
18
+ adapters and records exit codes/output hashes. Empty changes, zero tests, zero
19
+ assertions, skips, failing results and missing observations block. One identical
20
+ command executes once per run, even when it reports several scenarios.
21
+ 5. `check` revalidates the policy, receipt, note, entire source snapshot, environment
22
+ identity and required scenario inventory. A relevant edit invalidates the result.
23
+ 6. Completion requires PASS. An unavailable environment is BLOCKED, with a truthful
24
+ incomplete handoff permitted; it must not be described as completed verification.
25
+
26
+ Risk floors:
27
+
28
+ | Risk | Required scenarios |
29
+ | --- | --- |
30
+ | docs | inspection |
31
+ | logic | success, invalid, boundary |
32
+ | api | success, invalid, contract |
33
+ | db | success, denied, invalid, readback, rollback, migration_upgrade, migration_fresh |
34
+ | auth | success, denied, cross_scope, revoked |
35
+ | ui | success, error |
36
+ | release | artifact, integrity |
37
+
38
+ Path heuristics are conservative supplementary detection, not a complete dependency
39
+ or semantic graph. SQL functions/procedures/policies/triggers found in the old and
40
+ new SQL file must have explicit targets, e.g. `function:public.receiving`. Quoted
41
+ identifiers and dynamic SQL currently block for manual review; overloaded signatures,
42
+ indirect effects and application-specific dependencies require explicit project tests.
43
+ Do not label a function passing merely because a broad file-level test passed.
44
+
45
+ ## Commands and activation
46
+
47
+ ```bash
48
+ mkdir -p testing
49
+ yam template test-check > testing/policy.json
50
+ # Edit policy and implement the referenced adapter; capture BASE before editing.
51
+ yam test-check plan --base "$BASE" --json
52
+ yam test-check run --base "$BASE" --json
53
+ yam test-check check --json
54
+ ```
55
+
56
+ `BASE` must be an exact existing commit ID. A base equal to the candidate with no
57
+ changes cannot authorize completion. Generated/private `.yam`, dist, build, coverage,
58
+ node_modules, host agent configs and tarball/log files are excluded from source
59
+ snapshots; runtime dependency/configuration identities belong in environment evidence.
60
+ Source symlinks/submodules are unsupported and block, rather than silently omitted.
61
+
62
+ For a configured project, the existing opt-in `study-note` prompt/Stop hook also
63
+ runs Test check validation. Enable through `yam hook enable study-note --project .`
64
+ (or use an existing global study-note hook). Prompt captures changed-turn scope;
65
+ Stop checks the receipt only, never launches long tests. Unlike the report-format
66
+ one-correction rule, Test check remains blocked on repeated Stop calls. A response
67
+ containing the marker `Test check: BLOCKED` on its own status line may finish as an incomplete-status
68
+ handoff. This marker grants no machine gate approval. No-change/read-only turns
69
+ skip the completion check. Without a configured policy/hook the route instruction
70
+ is guidance, not host enforcement; missing host discovery must be reported.
71
+
72
+ Before commit use `check --stage commit`: the index must match the tested working
73
+ snapshot. Before push use `check --stage push --source "$SOURCE"` for every outgoing
74
+ commit; SOURCE must be an exact commit ID and match the tested snapshot. `check --base <expected starting commit>` rejects a receipt from a different baseline;
75
+ Security check supplies the actual CI base automatically. Stage ci
76
+ or deploy additionally requires an administrator-reviewed `--policy-digest`.
77
+ Missing CI/deploy pins are rejected before any project test command executes.
78
+ `run` supports the same stage/source requirements. Deploy additionally requires
79
+ `--artifact <repository-local-file> --configuration <repository-local-file>`
80
+ and a deployment-stage receipt. Both files are measured before/after the tests
81
+ and remeasured on check; completion-only evidence cannot approve deployment.
82
+ Adapters receive YAM_TEST_ARTIFACT / YAM_TEST_CONFIGURATION and must test those
83
+ exact inputs rather than create a substitute package. Run stage checks before operations,
84
+ not concurrently with index/source changes. Existing Security check invokes this
85
+ validation automatically when a default project policy exists; CI/deploy callers
86
+ must supply `yam security check --test-policy-digest "$PIN"`. Existing custom project
87
+ runners must generate the Test check receipt first. Do not overwrite other hooks.
88
+
89
+ Pins bind reviewed bytes; they do not independently approve policy authorship.
90
+ Policy/scenario removal and changed test adapters require renewed engineering review
91
+ and CI-protected policy approval. CI must independently run the reviewed tests;
92
+ local hooks/Stop hooks can be bypassed or disabled. This feature does not configure
93
+ GitHub protection or change installed global skills/hooks by package installation.
94
+
95
+ ## Adapter protocol
96
+
97
+ A command emits one JSON document on stdout (logs can go to stderr). Each selected
98
+ check must appear exactly once. Reports are data and are never executed.
99
+
100
+ ```json
101
+ {
102
+ "schema": "yam.test-observation.v1",
103
+ "tests": [{
104
+ "id": "save-success",
105
+ "target": "function:public.receiving",
106
+ "scenario": "success",
107
+ "evidence": "integration",
108
+ "status": "passed",
109
+ "tests": 1,
110
+ "assertions": 4,
111
+ "failed": 0,
112
+ "skipped": 0,
113
+ "mocked": false,
114
+ "environment": {}
115
+ }]
116
+ }
117
+ ```
118
+
119
+ The empty environment above is illustrative and cannot pass DB validation. DB
120
+ checks require `environment` to point to a repository-local expected manifest,
121
+ preferably ignored `.yam/testing/environment.json`, containing:
122
+
123
+ - schema `yam.test-environment.v1`, engine and exact engine_version;
124
+ - source_digest from `yam test-check plan`;
125
+ - configuration_digest, roles_digest and fixtures_digest (`sha256:...`);
126
+ - migrations: the complete sorted candidate `migrations/` inventory, each with
127
+ file and SHA256. Existing baseline migrations must also be included.
128
+
129
+ The adapter queries the *actual* isolated environment and returns the same structured
130
+ fields only after comparing applied migration order/content, runtime/roles/settings
131
+ and synthetic fixture identity. Returning the expected file without measuring the
132
+ DB is not valid evidence. yam compares reports and candidate manifests; adapter
133
+ assertions and environment truth are still trusted project code, not independently
134
+ attested by the generic runner. Misreported `mocked:false` cannot be detected by a
135
+ JSON validator. Review and protect the adapters as rigorously as the application.
136
+
137
+ Use actual DB operations with synthetic records and representative roles. Success
138
+ means saved headers/items and separate readback match, not just HTTP 200. Verify
139
+ failure rollback, duplicate/retry behavior, revoked access and relevant triggers.
140
+ Do not use owner/service credentials to stand in for ordinary-user authorization.
141
+ Test in disposable environments. Transaction rollback does not undo email, external
142
+ files, webhooks or all sequence effects; verify external isolation and cleanup.
143
+
144
+ ## Records, safety and limits
145
+
146
+ Each run appends an explained `-- Test check --` block to private
147
+ `.yam/testing/notes.md`, writes an immutable JSON under `runs/` and updates
148
+ `latest.json`. Note/receipt write failure blocks; concurrent persistence uses an
149
+ exclusive lock, rejects links/hardlinks and never removes someone else's lock.
150
+ Raw stdout/stderr, command arguments and environment values are not persisted;
151
+ only allowlisted facts and hashes are recorded. Do not put secrets in explanations
152
+ or command arguments. Use the project's existing least-privilege test environment.
153
+
154
+ A checksum is not a signature against a hostile same-user process. Timeout marks a
155
+ command failed; descendant-process cleanup is not attested by spawnSync. Native
156
+ JUnit/Playwright/Postgres result parsers, property/stateful/combination testing and
157
+ mutation testing are project adapters or future extensions, not built-in scanners.
158
+ The regression suite uses real Git/process/filesystem fixtures and simulated DB
159
+ observations; it does not claim to have tested any production RO database.
160
+
161
+ Implementation boundaries: existing verification-closure remains an immutable
162
+ operator-declared command receipt. Test check adds actual process execution and
163
+ scenario checks without promoting those older receipts into runtime proof.
164
+
165
+ ## Hosted rollout
166
+
167
+ The yam repository workflow runs Test check before Security check and uploads its
168
+ sanitized notes/runs. Activation after review requires `YAM_TEST_POLICY_SHA256`
169
+ alongside the existing reviewed source/security-policy pins. This change does not
170
+ set repository variables, update global hooks, commit, push or publish. An absent
171
+ or outdated pin intentionally blocks hosted approval rather than silently using
172
+ candidate-computed trust. Local project source-review manifests must be refreshed
173
+ only after the executable changes have been reviewed.
174
+
175
+ ## Optional real PostgreSQL simulation
176
+
177
+ From a source checkout, `npm run test-check-postgres-smoke` requires Docker access and an existing local
178
+ `postgres:17-alpine` image (override with YAM_TEST_PG_IMAGE). It never pulls an
179
+ image, publishes a port, mounts host data, or connects to a production database.
180
+ The disposable database reproduces SQLSTATE 42702 after successful function DDL,
181
+ then verifies fixed normal save/readback, access denial/revocation, invalid input,
182
+ rollback, retry, fresh/upgrade migrations, out-of-band function drift, and a
183
+ 16-case role/brand/quantity matrix. The
184
+ adapter queries actual server/function/ACL/migration state rather than echoing an
185
+ expected manifest. Cleanup must confirm the owned container is absent.
186
+ This is real PostgreSQL with synthetic application data, not verification of any
187
+ production application's SQL or authorization rules. It stays opt-in, outside
188
+ ordinary verification so a missing Docker daemon never silently becomes a mock.
189
+
190
+ Invalid stage/base values are rejected before persistence. Malformed run options
191
+ leave a sanitized failed note. Git discovery failure does not disable a previously
192
+ configured Stop gate; quoted examples of the handoff marker cannot approve it.
@@ -74,6 +74,10 @@ Stop condition:
74
74
  - Stop earlier when the same blocker repeats, a required tool/auth/runtime is unavailable, or a user decision is needed.
75
75
  - Do not keep retrying the same failure; classify the cause, give a recovery hint, and hand off.
76
76
 
77
+ ## Test check before completion
78
+
79
+ Before claiming changed work complete, use `references/test-check.md`. With a configured project policy run `yam test-check run --base <exact starting commit> --json` and `yam test-check check --json`. Require mapped scenarios, actual project-runner evidence, matching source/environment and a persisted Test check note. Required missing/failed/skipped checks block approval; give an honest `Test check: BLOCKED` handoff when unable to verify. Recheck exact staged/outgoing/deployment targets before their operations. Do not substitute mock DB results, report validation or old operator-declared receipts. If project adapters/policy are missing, report the activation gap instead of claiming gated completion. Read-only advisory work needs no execution gate; tiny changes use fitting inspection checks.
80
+
77
81
  ## Commit, push and deployment security gate
78
82
 
79
83
  Before performing a requested commit, push or deployment, follow `references/security-check.md`: validate stage-bound evidence with `yam security check`, require a successful persisted Security check note, and stop the operation when the gate blocks. If the project is not configured, report that blocker rather than claim security approval. Do not overwrite existing hooks or treat supplied evidence as an automatic vulnerability scan.
@@ -121,6 +121,10 @@ Good mission prompts include:
121
121
 
122
122
  If the user invokes `$mission` without an approved plan, ask for the plan or propose a compact plan first.
123
123
 
124
+ ## Test check before completion
125
+
126
+ Before claiming changed work complete, use `references/test-check.md`. With a configured project policy run `yam test-check run --base <exact starting commit> --json` and `yam test-check check --json`. Require mapped scenarios, actual project-runner evidence, matching source/environment and a persisted Test check note. Required missing/failed/skipped checks block approval; give an honest `Test check: BLOCKED` handoff when unable to verify. Recheck exact staged/outgoing/deployment targets before their operations. Do not substitute mock DB results, report validation or old operator-declared receipts. If project adapters/policy are missing, report the activation gap instead of claiming gated completion. Read-only advisory work needs no execution gate; tiny changes use fitting inspection checks.
127
+
124
128
  ## Commit, push and deployment security gate
125
129
 
126
130
  Before performing a requested commit, push or deployment, follow `references/security-check.md`: validate stage-bound evidence with `yam security check`, require a successful persisted Security check note, and stop the operation when the gate blocks. If the project is not configured, report that blocker rather than claim security approval. Do not overwrite existing hooks or treat supplied evidence as an automatic vulnerability scan.
@@ -75,6 +75,10 @@ Use `references/study-note.md` when any code, config, docs, or project artifact
75
75
  Use `references/final-report.md` to close with remaining tasks and fix-first items when useful.
76
76
  Use `references/token-budget-reporter.md` when a run needs measured budget feedback.
77
77
 
78
+ ## Test check before completion
79
+
80
+ Before claiming changed work complete, use `references/test-check.md`. With a configured project policy run `yam test-check run --base <exact starting commit> --json` and `yam test-check check --json`. Require mapped scenarios, actual project-runner evidence, matching source/environment and a persisted Test check note. Required missing/failed/skipped checks block approval; give an honest `Test check: BLOCKED` handoff when unable to verify. Recheck exact staged/outgoing/deployment targets before their operations. Do not substitute mock DB results, report validation or old operator-declared receipts. If project adapters/policy are missing, report the activation gap instead of claiming gated completion. Read-only advisory work needs no execution gate; tiny changes use fitting inspection checks.
81
+
78
82
  ## Commit, push and deployment security gate
79
83
 
80
84
  Before performing a requested commit, push or deployment, follow `references/security-check.md`: validate stage-bound evidence with `yam security check`, require a successful persisted Security check note, and stop the operation when the gate blocks. If the project is not configured, report that blocker rather than claim security approval. Do not overwrite existing hooks or treat supplied evidence as an automatic vulnerability scan.
@@ -183,6 +183,10 @@ Always consider:
183
183
  - Interaction affordance.
184
184
  - Consistency with the project's visual language.
185
185
 
186
+ ## Test check before completion
187
+
188
+ Before claiming changed work complete, use `references/test-check.md`. With a configured project policy run `yam test-check run --base <exact starting commit> --json` and `yam test-check check --json`. Require mapped scenarios, actual project-runner evidence, matching source/environment and a persisted Test check note. Required missing/failed/skipped checks block approval; give an honest `Test check: BLOCKED` handoff when unable to verify. Recheck exact staged/outgoing/deployment targets before their operations. Do not substitute mock DB results, report validation or old operator-declared receipts. If project adapters/policy are missing, report the activation gap instead of claiming gated completion. Read-only advisory work needs no execution gate; tiny changes use fitting inspection checks.
189
+
186
190
  ## Commit, push and deployment security gate
187
191
 
188
192
  Before performing a requested commit, push or deployment, follow `references/security-check.md`: validate stage-bound evidence with `yam security check`, require a successful persisted Security check note, and stop the operation when the gate blocks. If the project is not configured, report that blocker rather than claim security approval. Do not overwrite existing hooks or treat supplied evidence as an automatic vulnerability scan.
package/src/bin/yam.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { runTestCheckCommand, testCheckStop, testCheckConfigured } from '../lib/test-check.js';
2
3
  import { spawnSync } from 'node:child_process';
3
4
  import { studyNoteTurnScope } from '../lib/study-note-scope.js';
4
5
  import { selectHookNode, probeHookDiscovery, inspectHookObservation, recordHookObservation } from '../lib/hook-readiness.js';
@@ -228,6 +229,7 @@ Usage:
228
229
  yam proof [dir|--from file] [--route route] [--truth status] [--command text] [--evidence text]
229
230
  yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
230
231
  yam study-note check [dir] [--report file|--text text] [--json]
232
+ yam test-check <plan|run|check|status> [--base commit] [--json]
231
233
  yam security <init|check|hooks|status> [--stage commit|push|ci|deploy] [--json]
232
234
  yam next-step <report|verify> [--spec file|--receipt file] [--json]
233
235
  yam loop report [--route route] [--intent text] [--stage id:status:note] [--evidence text] [--json]
@@ -254,7 +256,7 @@ Usage:
254
256
  yam safety [text...]
255
257
  yam memory <init|add|list|summary|resolve> [dir] [options]
256
258
  yam hook <status|enable|disable|run> [lite|study-note] [--global|--project dir]
257
- yam template <project|ueye|mission|proof|tuning|security|security-ci>
259
+ yam template <project|ueye|mission|proof|tuning|security|security-ci|test-check>
258
260
  yam tune-log [dir]
259
261
  yam install [--dry-run] [--json] [--replace-user-skill name]
260
262
  yam uninstall
@@ -576,6 +578,7 @@ async function verify({ quiet = false } = {}) {
576
578
  'markdown-management.md',
577
579
  'study-note.md',
578
580
  'security-check.md',
581
+ 'test-check.md',
579
582
  'final-report.md',
580
583
  'token-budget-reporter.md',
581
584
  'memory.md'
@@ -591,7 +594,7 @@ async function verify({ quiet = false } = {}) {
591
594
  if (!hasHeading(projectTemplate, section)) issues.push(`project template missing section: ${section}`);
592
595
  }
593
596
  }
594
- for (const template of ['ueye-review.md', 'ueye-comparison.md', 'mission-plan.md', 'runtime-proof.md', 'tuning-log.md', 'security-check-note.md', 'security-gate.yml']) {
597
+ for (const template of ['ueye-review.md', 'ueye-comparison.md', 'mission-plan.md', 'runtime-proof.md', 'tuning-log.md', 'security-check-note.md', 'security-gate.yml', 'test-check-policy.json']) {
595
598
  if (!await exists(path.join(ROOT, 'templates', template))) issues.push(`missing template: ${template}`);
596
599
  }
597
600
  for (const module of ['trust-kernel.js', 'ueye-artifacts.js']) {
@@ -2524,6 +2527,8 @@ async function buildYamLiteContext({ cwd, prompt }) {
2524
2527
  async function buildStudyNoteStopOutput(input: AnyRecord = {}, cwd = process.cwd()) {
2525
2528
  const lastAssistantMessage = String(input?.last_assistant_message || input?.lastAssistantMessage || '');
2526
2529
  const scope = studyNoteTurnScope(input, cwd);
2530
+ const testGate = testCheckStop(cwd, !scope.available || scope.files.length > 0, lastAssistantMessage);
2531
+ if (testGate) return testGate;
2527
2532
  const result = await buildStudyNoteGuardResult(path.resolve(cwd), lastAssistantMessage, 'stop_hook_last_assistant_message', scope);
2528
2533
  if (result.truth_status === 'partial' && !result.changed_file_detection.available) {
2529
2534
  return {
@@ -2554,8 +2559,9 @@ async function buildStudyNoteStopOutput(input: AnyRecord = {}, cwd = process.cwd
2554
2559
 
2555
2560
  async function buildStudyNoteTurnContext(input: AnyRecord, cwd: string) {
2556
2561
  const scope = studyNoteTurnScope(input, cwd, true);
2562
+ const testReminder = testCheckConfigured(cwd) ? ' Test check active: before completion run yam test-check run --base <exact starting commit> --json and yam test-check check --json. Missing required tests/evidence block completion; report Test check: BLOCKED if unable to verify. This is separate from report formatting and Security check.' : '';
2557
2563
  if (!scope.available) return 'yam Study Note: turn scope is unavailable; do not treat this as a clean project. Inspect changed artifacts manually; no strict block will be applied without a baseline.';
2558
- return `yam Study Note turn baseline saved (hash-only). Mode: ${scope.mode}. Only changes since this prompt count; known generated logs, mission/security receipts and screenshots are excluded unless explicitly included. Include a concise Study Note followed by Next step when product artifacts change. ${scope.mode === 'strict' ? 'Risk work uses the detailed report contract and one correction pass.' : 'Ordinary work is advisory: a short explanation, verification/limits and remaining work (or none) suffice.'}`;
2564
+ return `yam Study Note turn baseline saved (hash-only). Mode: ${scope.mode}. Only changes since this prompt count; known generated logs, mission/security receipts and screenshots are excluded unless explicitly included. Include a concise Study Note followed by Next step when product artifacts change. ${scope.mode === 'strict' ? 'Risk work uses the detailed report contract and one correction pass.' : 'Ordinary work is advisory: a short explanation, verification/limits and remaining work (or none) suffice.'}${testReminder}`;
2559
2565
  }
2560
2566
 
2561
2567
  function yamLiteRouteHint(prompt = '') {
@@ -6149,11 +6155,12 @@ async function printTemplate(name = '') {
6149
6155
  runtime: 'runtime-proof.md',
6150
6156
  tuning: 'tuning-log.md',
6151
6157
  security: 'security-check-note.md',
6152
- 'security-ci': 'security-gate.yml'
6158
+ 'security-ci': 'security-gate.yml',
6159
+ 'test-check': 'test-check-policy.json'
6153
6160
  };
6154
6161
  const file = map[key];
6155
6162
  if (!file) {
6156
- console.error('usage: yam template <project|ueye|ueye-comparison|mission|proof|tuning|security|security-ci>');
6163
+ console.error('usage: yam template <project|ueye|ueye-comparison|mission|proof|tuning|security|security-ci|test-check>');
6157
6164
  process.exitCode = 1;
6158
6165
  return;
6159
6166
  }
@@ -6456,6 +6463,13 @@ function showRequestedHelp(args: string[]) {
6456
6463
  async function main() {
6457
6464
  const args = process.argv.slice(2);
6458
6465
  const command = args[0] || 'help';
6466
+ if (command === 'test-check') {
6467
+ if (!args[1] || ['help', '--help', '-h'].includes(args[1])) {
6468
+ console.log('yam test-check run --base <exact commit> [--policy file --policy-digest sha256] [--json]\nyam test-check check [--stage complete|commit|push|ci|deploy --source <exact commit> --policy-digest sha256 --receipt file] [--json]\nyam test-check status\nyam test-check plan --base <exact commit> --json\nSee references/test-check.md for required scenarios, adapters and activation.'); return;
6469
+ }
6470
+ const result = runTestCheckCommand(args.slice(1));
6471
+ console.log(JSON.stringify(result, null, 2)); if (!result.ok) process.exitCode = 1; return;
6472
+ }
6459
6473
  if (showRequestedHelp(args)) return;
6460
6474
  if (command === 'help') return usage();
6461
6475
  if (command === 'security') {
@@ -1,3 +1,4 @@
1
+ import { checkTestCheck, testCheckConfigured } from './test-check.js';
1
2
  import { createHash, randomUUID } from 'node:crypto';
2
3
  import { execFileSync } from 'node:child_process';
3
4
  import fs from 'node:fs/promises';
@@ -27,7 +28,7 @@ function parseArgs(args: string[]): Map<string, string> {
27
28
  const out = new Map<string, string>();
28
29
  for (let i = 0; i < args.length; i++) {
29
30
  if (args[i] === '--json') continue;
30
- if (!['--policy', '--policy-digest', '--evidence', '--note', '--stage', '--remote', '--destination', '--source', '--base', '--artifact', '--artifact-digest', '--configuration', '--configuration-digest', '--environment', '--cli'].includes(args[i]) || !args[i + 1] || args[i + 1].startsWith('--') || out.has(args[i])) throw new Error('invalid or duplicate security option');
31
+ if (!['--policy', '--policy-digest', '--evidence', '--note', '--stage', '--remote', '--destination', '--source', '--base', '--artifact', '--artifact-digest', '--configuration', '--configuration-digest', '--environment', '--cli', '--test-policy-digest'].includes(args[i]) || !args[i + 1] || args[i + 1].startsWith('--') || out.has(args[i])) throw new Error('invalid or duplicate security option');
31
32
  out.set(args[i], args[++i]);
32
33
  }
33
34
  return out;
@@ -262,6 +263,13 @@ async function check(cwd: string, options: Map<string, string>, settings: Securi
262
263
  result = validateSecurityCheck(input, binding as any);
263
264
  gateErrors.push('security_input_or_binding_unavailable');
264
265
  }
266
+ if (testCheckConfigured(cwd) || config.test_policy_digest) {
267
+ const pin = options.get('--test-policy-digest') || config.test_policy_digest;
268
+ const sources = stage === 'push' ? (binding.push_refs as any[] || []).map(ref => ref.local_oid)
269
+ : [binding.source_revision as string | undefined];
270
+ const targets = sources.length ? sources : [undefined];
271
+ if (targets.some(source => !checkTestCheck(cwd, { stage, source, pin, base: stage === 'ci' ? binding.base_revision as string : undefined, artifact: options.get('--artifact'), configuration: options.get('--configuration') }).ok)) gateErrors.push('test_check_blocked');
272
+ }
265
273
  if (gateErrors.length) {
266
274
  result.result = 'BLOCKED';
267
275
  result.errors = [...new Set([...result.errors, ...gateErrors])];