gentle-pi 2.1.2 → 2.2.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 +70 -12
- package/assets/agents/gentle-ai-worker.md +7 -3
- package/assets/agents/jd-fix-agent.md +1 -1
- package/assets/agents/jd-judge-a.md +3 -1
- package/assets/agents/jd-judge-b.md +3 -1
- package/assets/agents/review-readability.md +4 -1
- package/assets/agents/review-reliability.md +4 -1
- package/assets/agents/review-resilience.md +4 -1
- package/assets/agents/review-risk.md +4 -1
- package/assets/agents/sdd-apply.md +6 -1
- package/assets/agents/sdd-archive.md +6 -1
- package/assets/agents/sdd-design.md +6 -1
- package/assets/agents/sdd-explore.md +6 -2
- package/assets/agents/sdd-init.md +10 -2
- package/assets/agents/sdd-onboard.md +6 -1
- package/assets/agents/sdd-proposal.md +6 -1
- package/assets/agents/sdd-spec.md +6 -1
- package/assets/agents/sdd-status.md +6 -1
- package/assets/agents/sdd-sync.md +6 -1
- package/assets/agents/sdd-tasks.md +6 -1
- package/assets/agents/sdd-verify.md +6 -1
- package/assets/chains/4r-review.chain.md +2 -0
- package/assets/chains/sdd-full.chain.md +1 -1
- package/assets/chains/sdd-plan.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +1 -1
- package/assets/orchestrator-delegation.md +246 -67
- package/assets/orchestrator.md +7 -14
- package/assets/sdd-orchestrator-workflow.md +154 -9
- package/assets/support/sdd-status-contract.md +19 -1
- package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
- package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
- package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
- package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
- package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
- package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
- package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
- package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
- package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
- package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
- package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
- package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
- package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
- package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
- package/docs/native-authority-architecture.md +5 -5
- package/docs/review-integration.md +22 -2
- package/extensions/gentle-ai.ts +1595 -201
- package/extensions/sdd-init.ts +19 -6
- package/extensions/skill-registry.ts +10 -2
- package/extensions/startup-banner.ts +10 -4
- package/lib/gentle-ai-binary.ts +173 -2
- package/lib/git-commit-transaction.ts +77 -17
- package/lib/native-review-cli.ts +528 -65
- package/lib/provider-contract-bundle.ts +704 -0
- package/lib/review-candidate-view.ts +527 -18
- package/lib/review-compact-contract.ts +59 -248
- package/lib/review-host-relay.ts +436 -0
- package/lib/review-integration-v2.ts +537 -36
- package/lib/review-relay-contract.ts +16 -0
- package/lib/sdd-preflight.ts +53 -1
- package/package.json +5 -2
- package/runtime/gentle-ai-binary.mjs +173 -2
- package/runtime/git-commit-transaction.mjs +75 -15
- package/runtime/native-review-cli.mjs +524 -61
- package/runtime/review-integration-v2.mjs +536 -35
- package/runtime/review-relay-contract.mjs +17 -0
- package/scripts/build-git-commit-transaction-runner.mjs +1 -0
- package/scripts/check-provider-contract.mjs +138 -0
- package/scripts/gentle-ai-installer.mjs +23 -13
- package/scripts/maintainer/provider-relay-matrix.mjs +219 -0
- package/scripts/mirror-provider-contract.mjs +143 -0
- package/scripts/test-packed-runner.mjs +16 -2
- package/scripts/verify-package-files.mjs +110 -33
- package/skills/_shared/review-ledger-contract.md +4 -6
- package/skills/gentle-ai/SKILL.md +4 -4
- package/skills/issue-creation/SKILL.md +94 -168
- package/skills/judgment-day/SKILL.md +7 -1
- package/skills/judgment-day/references/prompts-and-formats.md +2 -0
- package/skills/rdd-defect-workflow/SKILL.md +54 -0
- package/tests/background-subagents.test.ts +771 -0
- package/tests/crosslane/cross-lane.mjs +1169 -0
- package/tests/delegated-key-learnings-contract.test.ts +238 -0
- package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
- package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
- package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
- package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
- package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
- package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
- package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
- package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
- package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
- package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
- package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
- package/tests/gentle-ai-dev-binary.test.ts +336 -0
- package/tests/gentle-ai-installer.test.ts +46 -46
- package/tests/git-commit-transaction.test.ts +229 -1
- package/tests/maintainer/provider-relay.maintest.ts +265 -0
- package/tests/native-review-capability-contract.test.ts +48 -2
- package/tests/native-review-cli.test.ts +56 -0
- package/tests/native-review-consent.test.ts +164 -3
- package/tests/native-review-parity-runtime.test.ts +37 -0
- package/tests/native-review-parity.test.ts +218 -15
- package/tests/native-sdd-attempt-authority.test.ts +235 -0
- package/tests/orchestrator-budget.test.ts +30 -5
- package/tests/package-manifest.test.ts +98 -72
- package/tests/provider-contract-bundle.test.ts +385 -0
- package/tests/provider-contract-mirror.test.ts +206 -0
- package/tests/provider-defect-handoff.test.ts +355 -0
- package/tests/review-actor-tool-deny.test.ts +12 -13
- package/tests/review-candidate-view.test.ts +489 -9
- package/tests/review-compact-contract.test.ts +52 -119
- package/tests/review-controller-native-recovery.test.ts +643 -47
- package/tests/review-controller-native-routing.test.ts +1667 -222
- package/tests/review-controller-workspace-root.test.ts +17 -2
- package/tests/review-corrected-finalize-binding.test.ts +175 -0
- package/tests/review-dispatch-hydration-gap.test.ts +197 -0
- package/tests/review-host-relay-routing.test.ts +317 -0
- package/tests/review-host-relay.test.ts +520 -0
- package/tests/review-integration-v2-forward.test.ts +631 -0
- package/tests/review-integration-v2.test.ts +114 -0
- package/tests/review-ledger-contract.test.ts +12 -28
- package/tests/review-recovered-lineage-routing.test.ts +246 -0
- package/tests/review-relay-transport-agent.test.ts +249 -0
- package/tests/runtime-harness.mjs +242 -14
- package/tests/sdd-agent-tools.test.ts +18 -33
- package/tests/skill-collision-prefixes.test.ts +1 -0
- package/tests/skill-registry.test.ts +50 -1
- package/tests/verify-package-files.test.ts +62 -0
- package/assets/agents/review-refuter.md +0 -40
- package/assets/agents/review-validator.md +0 -23
- package/lib/native-review-remediation.ts +0 -49
- package/lib/review-compact.ts +0 -947
- package/lib/review-refuter-adapter.ts +0 -129
- package/lib/review-runtime-contract.ts +0 -68
- package/prompts/gcl.md +0 -54
- package/prompts/gis.md +0 -25
- package/prompts/gpr.md +0 -41
- package/prompts/gwr.md +0 -31
- package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
- package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
- package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
- package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
- package/tests/review-compact.test.ts +0 -243
- package/tests/review-refuter-adapter.test.ts +0 -89
|
@@ -94,23 +94,38 @@ export const REVIEW_STATUS_ACTION_DISPOSITION = {
|
|
|
94
94
|
|
|
95
95
|
const RECEIPT_STATUSES = ["expected_missing", "present", "publication_pending", "not_applicable"] ;
|
|
96
96
|
const REQUIRED_OPERATIONS = Object.freeze(Object.values(REVIEW_INTEGRATION_OPERATION));
|
|
97
|
+
// failure/v2 operations: the capability floor plus the four collect-capture
|
|
98
|
+
// verbs that emit typed refusals on the gentle-ai main line (commit a2d57117,
|
|
99
|
+
// fix/capture-evidence-typed-refusal; exact strings from that branch's
|
|
100
|
+
// published failure.schema.json 12-value operation enum). Additive forward
|
|
101
|
+
// surface: every operation outside the published enum still rejects.
|
|
102
|
+
const FAILURE_OPERATIONS = Object.freeze([
|
|
103
|
+
...REQUIRED_OPERATIONS,
|
|
104
|
+
"review.capture-result",
|
|
105
|
+
"review.capture-evidence",
|
|
106
|
+
"review.capture-refuter",
|
|
107
|
+
"review.capture-validation",
|
|
108
|
+
] );
|
|
97
109
|
const REQUIRED_GATES = Object.freeze(["post-apply", "pre-commit", "pre-push", "pre-pr", "release"] );
|
|
98
110
|
const REQUIRED_PROJECTIONS = Object.freeze(Object.values(REVIEW_PROJECTION));
|
|
99
|
-
|
|
111
|
+
// The schema floor shared by every accepted capabilities identity. Each minor
|
|
112
|
+
// then adds its own capabilities/consent/status identities below: the provider
|
|
113
|
+
// swaps those three advertisements as the protocol minor advances (v2.1 moved
|
|
114
|
+
// consent to v3; v2.2 moved status to v5), ground-truthed against the vendored
|
|
115
|
+
// capabilities[-v2.1|-v2.2].schema.json contracts on gentle-ai main and a live
|
|
116
|
+
// v2.2 capture from a main-line dev build.
|
|
117
|
+
const REQUIRED_SCHEMAS_COMMON = Object.freeze([
|
|
100
118
|
"gentle-ai.review-admitted-result/v2",
|
|
101
119
|
"gentle-ai.review-artifact-subject/v2",
|
|
102
120
|
"gentle-ai.review-authority-repair-assessment/v1",
|
|
103
121
|
"gentle-ai.review-authority-status/v1",
|
|
104
122
|
"gentle-ai.review-gate-request/v1",
|
|
105
|
-
"gentle-ai.review-integration.capabilities/v2",
|
|
106
|
-
"gentle-ai.review-integration.consent/v2",
|
|
107
123
|
"gentle-ai.review-integration.failure/v2",
|
|
108
124
|
"gentle-ai.review-final-verification-incident/v1",
|
|
109
125
|
"gentle-ai.review-integration.operation/v2",
|
|
110
126
|
"gentle-ai.review-integration.projection/v1",
|
|
111
127
|
"gentle-ai.review-integration.repair/v2",
|
|
112
128
|
"gentle-ai.review-integration.start/v3",
|
|
113
|
-
"gentle-ai.review-integration.status/v3",
|
|
114
129
|
"gentle-ai.review-receipt/v1",
|
|
115
130
|
"gentle-ai.review-receipt/v2",
|
|
116
131
|
"gentle-ai.review-result-artifact/v2",
|
|
@@ -120,6 +135,20 @@ const REQUIRED_SCHEMAS = Object.freeze([
|
|
|
120
135
|
"https://gentle-ai.dev/schema/review/reviewer/v1",
|
|
121
136
|
"https://gentle-ai.dev/schema/review/validator/v1",
|
|
122
137
|
] );
|
|
138
|
+
const CAPABILITIES_SCHEMA_IDENTITIES = Object.freeze({
|
|
139
|
+
"gentle-ai.review-integration.capabilities/v2": Object.freeze({
|
|
140
|
+
protocolMinor: 0,
|
|
141
|
+
requiredSchemas: Object.freeze([...REQUIRED_SCHEMAS_COMMON, "gentle-ai.review-integration.capabilities/v2", "gentle-ai.review-integration.consent/v2", "gentle-ai.review-integration.status/v3"]),
|
|
142
|
+
}),
|
|
143
|
+
"gentle-ai.review-integration.capabilities/v2.1": Object.freeze({
|
|
144
|
+
protocolMinor: 1,
|
|
145
|
+
requiredSchemas: Object.freeze([...REQUIRED_SCHEMAS_COMMON, "gentle-ai.review-integration.capabilities/v2.1", "gentle-ai.review-integration.consent/v3", "gentle-ai.review-integration.status/v3"]),
|
|
146
|
+
}),
|
|
147
|
+
"gentle-ai.review-integration.capabilities/v2.2": Object.freeze({
|
|
148
|
+
protocolMinor: 2,
|
|
149
|
+
requiredSchemas: Object.freeze([...REQUIRED_SCHEMAS_COMMON, "gentle-ai.review-integration.capabilities/v2.2", "gentle-ai.review-integration.consent/v3", "gentle-ai.review-integration.status/v5"]),
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
123
152
|
const OPTIONAL_FEATURE_NAMES = Object.freeze([
|
|
124
153
|
"base_ref_workspace_overlay",
|
|
125
154
|
"bounded_process_waits",
|
|
@@ -246,6 +275,131 @@ const REQUIRED_MANDATORY_FEATURES = Object.freeze(FEATURE_NAMES.filter((name) =>
|
|
|
246
275
|
|
|
247
276
|
|
|
248
277
|
|
|
278
|
+
const REPOSITORY_CONTEXT_OUTCOMES = ["applied", "pending", "blocked_conflict", "durability_limited"] ;
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
// Provider-owned completing form for a host-mediated capture slot
|
|
357
|
+
// (gentle-pi#311 P4). The provider issues the exact operation and argument
|
|
358
|
+
// tokens that submit the captured bytes; the host substitutes only the
|
|
359
|
+
// artifact location into the declared {{value}} slot and never synthesizes
|
|
360
|
+
// or filters the form itself.
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
// The two Go-owned non-lens provider role capture operations (gentle-pi#311
|
|
382
|
+
// P4-roles; provider side gentle-ai#3264). Their collect inputs are
|
|
383
|
+
// self-contained authority-advancing vectors: binding tokens plus
|
|
384
|
+
// `--agent=pi --execute=true`, with NO submission descriptor. The known set
|
|
385
|
+
// is closed — an unknown role capture operation is never executed.
|
|
386
|
+
export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATION = {
|
|
387
|
+
CAPTURE_REFUTER: "review.capture-refuter",
|
|
388
|
+
CAPTURE_VALIDATION: "review.capture-validation",
|
|
389
|
+
} ;
|
|
390
|
+
|
|
391
|
+
export const REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS = Object.freeze(Object.values(REVIEW_PROVIDER_ROLE_CAPTURE_OPERATION));
|
|
392
|
+
|
|
393
|
+
// status/v5 structural surfaces. Pi has no consumer for these yet; they are
|
|
394
|
+
// decoded strictly (exact key sets per the vendored status-v5.schema.json and
|
|
395
|
+
// correction-plan-request.schema.json on gentle-ai main) and carried through
|
|
396
|
+
// so a v5 provider payload is never rejected for being newer than v3.
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
|
|
249
403
|
|
|
250
404
|
|
|
251
405
|
|
|
@@ -268,6 +422,10 @@ const REQUIRED_MANDATORY_FEATURES = Object.freeze(FEATURE_NAMES.filter((name) =>
|
|
|
268
422
|
|
|
269
423
|
|
|
270
424
|
|
|
425
|
+
// The two v5 Go-owned submission descriptor forms (finalize carries one
|
|
426
|
+
// singular `value`; capture-evidence carries an outcome/input `values` pair).
|
|
427
|
+
// Distinct from the host-relay ReviewCaptureSubmissionV1 form, which stays
|
|
428
|
+
// exactly as it is.
|
|
271
429
|
|
|
272
430
|
|
|
273
431
|
|
|
@@ -371,6 +529,7 @@ const REQUIRED_MANDATORY_FEATURES = Object.freeze(FEATURE_NAMES.filter((name) =>
|
|
|
371
529
|
|
|
372
530
|
|
|
373
531
|
|
|
532
|
+
|
|
374
533
|
|
|
375
534
|
|
|
376
535
|
|
|
@@ -402,6 +561,19 @@ const REQUIRED_MANDATORY_FEATURES = Object.freeze(FEATURE_NAMES.filter((name) =>
|
|
|
402
561
|
|
|
403
562
|
|
|
404
563
|
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
// consent/v3 (gentle-ai >= 2.3.0, capabilities/v2.1+): the same per-candidate
|
|
567
|
+
// blocking question with one net-new required member, the provider-fixed
|
|
568
|
+
// `agent` runtime binding. Everything shared with v2 keeps its exact shape so
|
|
569
|
+
// consumers of either identity read one structural surface.
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
// Either accepted consent identity. Consumers that relay the envelope (rather
|
|
576
|
+
// than decode it) accept both; each decoder still admits exactly one schema.
|
|
405
577
|
|
|
406
578
|
|
|
407
579
|
const FAILURE_REQUIRED_INPUTS = [
|
|
@@ -548,6 +720,8 @@ const FAILURE_CAUSE_CATEGORIES = ["inventory_io_or_layout", "lock_ambiguous", "r
|
|
|
548
720
|
|
|
549
721
|
|
|
550
722
|
|
|
723
|
+
|
|
724
|
+
|
|
551
725
|
|
|
552
726
|
|
|
553
727
|
|
|
@@ -690,10 +864,15 @@ function decodeOptionalFeature(value , label )
|
|
|
690
864
|
export function decodeReviewCapabilitiesV2(value , verifiedExecutableDigest ) {
|
|
691
865
|
const requiredFields = ["schema", "contract", "protocol", "package", "build", "executable", "operations", "gates", "projections", "schemas", "features", "compatibility"] ;
|
|
692
866
|
const body = exactRecord(value, "capabilities", requiredFields, ["bootstrap"]);
|
|
693
|
-
|
|
867
|
+
// Additive minor acceptance: each accepted schema identity keeps its own
|
|
868
|
+
// exact protocol minor and required-schema floor; v2 stays exactly as it
|
|
869
|
+
// was, and an unknown identity is rejected before anything else.
|
|
870
|
+
const identity = CAPABILITIES_SCHEMA_IDENTITIES[typeof body.schema === "string" ? body.schema : ""];
|
|
871
|
+
if (identity === undefined) throw new TypeError(`schema must be one of ${Object.keys(CAPABILITIES_SCHEMA_IDENTITIES).join(", ")}`);
|
|
872
|
+
requireIdentity(body, body.schema );
|
|
694
873
|
|
|
695
874
|
const protocol = exactRecord(body.protocol, "capabilities.protocol", ["major", "minor"]);
|
|
696
|
-
if (protocol.major !== 2 || protocol.minor !==
|
|
875
|
+
if (protocol.major !== 2 || protocol.minor !== identity.protocolMinor) throw new TypeError("incompatible review integration protocol");
|
|
697
876
|
|
|
698
877
|
const packageIdentity = exactRecord(body.package, "capabilities.package", ["name", "version", "release_channel"]);
|
|
699
878
|
if (packageIdentity.name !== "gentle-ai") throw new TypeError("capabilities package identity mismatch");
|
|
@@ -713,13 +892,18 @@ export function decodeReviewCapabilitiesV2(value , verifiedExecutableDig
|
|
|
713
892
|
if (selfReportedDigest !== normalizedVerifiedDigest) throw new TypeError("review provider executable digest mismatch");
|
|
714
893
|
|
|
715
894
|
const advertisedOperations = stringArray(body.operations, "capabilities.operations", { minimum: REQUIRED_OPERATIONS.length, unique: true });
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
895
|
+
// Gates and projections are, like operations and schemas, a superset promise
|
|
896
|
+
// rather than an exact manifest: a compatible provider release may advertise
|
|
897
|
+
// an additional gate or projection name beyond the required floor. Decode as
|
|
898
|
+
// a plain string array (not `enumArray` against the known enum) so an
|
|
899
|
+
// unknown addition is not rejected before assertSupersetOf can even run.
|
|
900
|
+
const advertisedGates = stringArray(body.gates, "capabilities.gates", { minimum: REQUIRED_GATES.length, unique: true });
|
|
901
|
+
const advertisedProjections = stringArray(body.projections, "capabilities.projections", { minimum: REQUIRED_PROJECTIONS.length, unique: true });
|
|
902
|
+
const advertisedSchemas = stringArray(body.schemas, "capabilities.schemas", { minimum: identity.requiredSchemas.length, unique: true });
|
|
719
903
|
assertSupersetOf(advertisedOperations, REQUIRED_OPERATIONS, "capabilities operations");
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
assertSupersetOf(advertisedSchemas,
|
|
904
|
+
assertSupersetOf(advertisedGates, REQUIRED_GATES, "capabilities gates");
|
|
905
|
+
assertSupersetOf(advertisedProjections, REQUIRED_PROJECTIONS, "capabilities projections");
|
|
906
|
+
assertSupersetOf(advertisedSchemas, identity.requiredSchemas, "capabilities schemas");
|
|
723
907
|
|
|
724
908
|
const features = exactRecord(body.features, "capabilities.features", ["mandatory", "optional"]);
|
|
725
909
|
const mandatory = array(features.mandatory, "capabilities.features.mandatory", (entry, label) => decodeFeature(entry, label), { minimum: 10 });
|
|
@@ -765,9 +949,9 @@ export function decodeReviewCapabilitiesV2(value , verifiedExecutableDig
|
|
|
765
949
|
buildId,
|
|
766
950
|
executableDigest: selfReportedDigest,
|
|
767
951
|
operations: new Set(REQUIRED_OPERATIONS),
|
|
768
|
-
gates: new Set(
|
|
769
|
-
projections: new Set(
|
|
770
|
-
schemas: new Set(
|
|
952
|
+
gates: new Set(REQUIRED_GATES),
|
|
953
|
+
projections: new Set(REQUIRED_PROJECTIONS),
|
|
954
|
+
schemas: new Set(identity.requiredSchemas),
|
|
771
955
|
mandatoryFeatures: new Set(mandatoryNames),
|
|
772
956
|
optionalFeatures: new Set(optional.filter((feature) => feature.supported && (FEATURE_NAMES ).includes(feature.name)).map((feature) => feature.name)),
|
|
773
957
|
raw: body,
|
|
@@ -858,13 +1042,15 @@ export function decodeReviewStartV3(value ) {
|
|
|
858
1042
|
|
|
859
1043
|
let repositoryContext ;
|
|
860
1044
|
if (body.repository_context !== undefined) {
|
|
861
|
-
const source = exactRecord(body.repository_context, "start.repository_context", ["capability", "handle", "revision", "target_identity"]);
|
|
1045
|
+
const source = exactRecord(body.repository_context, "start.repository_context", ["capability", "handle", "revision", "target_identity"], ["event_id", "outcome"]);
|
|
862
1046
|
if (source.capability !== "review.opaque_repository_context") throw new TypeError("start.repository_context.capability is unsupported");
|
|
863
1047
|
repositoryContext = {
|
|
864
1048
|
capability: "review.opaque_repository_context",
|
|
865
1049
|
handle: text(source.handle, "start.repository_context.handle", { pattern: /^rctx1_[0-9a-f]{64}$/ }),
|
|
866
1050
|
revision: sha256(source.revision, "start.repository_context.revision"),
|
|
867
1051
|
targetIdentity: sha256(source.target_identity, "start.repository_context.target_identity"),
|
|
1052
|
+
...(source.event_id === undefined ? {} : { eventId: sha256(source.event_id, "start.repository_context.event_id") }),
|
|
1053
|
+
...(source.outcome === undefined ? {} : { outcome: enumeration(source.outcome, REPOSITORY_CONTEXT_OUTCOMES, "start.repository_context.outcome") }),
|
|
868
1054
|
};
|
|
869
1055
|
}
|
|
870
1056
|
|
|
@@ -1043,8 +1229,146 @@ function decodeTransitionArguments(value , label )
|
|
|
1043
1229
|
});
|
|
1044
1230
|
}
|
|
1045
1231
|
|
|
1046
|
-
function
|
|
1047
|
-
|
|
1232
|
+
function decodeCaptureSubmission(value , label , v5 ) {
|
|
1233
|
+
// status/v5: the live 2.4.0-main binary emits the materialize
|
|
1234
|
+
// capture-result submission as a SINGULAR `value` object carrying a
|
|
1235
|
+
// `schema` key (captured 2026-08-16 from 2.4.0-main.b1afef46; the
|
|
1236
|
+
// emitter has been singular since gentle-ai f1a95179). The form is
|
|
1237
|
+
// closed to that exact captured shape and normalizes into the typed
|
|
1238
|
+
// one-entry values array the host relay already consumes. A payload
|
|
1239
|
+
// carrying both wire forms at once matches no captured shape and falls
|
|
1240
|
+
// through to the legacy decoder, which rejects the unknown `value` key.
|
|
1241
|
+
if (v5 && typeof value === "object" && value !== null && "value" in value && !("values" in value)) {
|
|
1242
|
+
const submission = exactRecord(value, label, ["operation_token", "argument_tokens", "value"]);
|
|
1243
|
+
if (submission.operation_token !== "capture-result") throw new TypeError(`${label}.operation_token must be capture-result for the singular value form`);
|
|
1244
|
+
const argumentTokens = stringArray(submission.argument_tokens, `${label}.argument_tokens`, { minimum: 1 });
|
|
1245
|
+
const row = exactRecord(submission.value, `${label}.value`, ["slot", "domain", "schema", "substitution_location"]);
|
|
1246
|
+
return {
|
|
1247
|
+
operationToken: "capture-result",
|
|
1248
|
+
argumentTokens,
|
|
1249
|
+
values: [{
|
|
1250
|
+
slot: enumeration(row.slot, ["reviewer_result"] , `${label}.value.slot`),
|
|
1251
|
+
domain: nonempty(row.domain, `${label}.value.domain`),
|
|
1252
|
+
schema: nonempty(row.schema, `${label}.value.schema`),
|
|
1253
|
+
substitutionLocation: integer(row.substitution_location, `${label}.value.substitution_location`, 0, argumentTokens.length - 1),
|
|
1254
|
+
}],
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
const submission = exactRecord(value, label, ["operation_token", "argument_tokens", "values"]);
|
|
1258
|
+
const operationToken = text(submission.operation_token, `${label}.operation_token`, { minimum: 1, pattern: /^[a-z0-9-]+$/ });
|
|
1259
|
+
const argumentTokens = stringArray(submission.argument_tokens, `${label}.argument_tokens`, { minimum: 1 });
|
|
1260
|
+
const values = array(submission.values, `${label}.values`, (entry, entryLabel) => {
|
|
1261
|
+
const row = exactRecord(entry, entryLabel, ["slot", "domain", "substitution_location"]);
|
|
1262
|
+
return {
|
|
1263
|
+
slot: nonempty(row.slot, `${entryLabel}.slot`),
|
|
1264
|
+
domain: nonempty(row.domain, `${entryLabel}.domain`),
|
|
1265
|
+
substitutionLocation: integer(row.substitution_location, `${entryLabel}.substitution_location`, 0, argumentTokens.length - 1),
|
|
1266
|
+
};
|
|
1267
|
+
}, { minimum: 1 });
|
|
1268
|
+
return { operationToken, argumentTokens, values };
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// v5-only structural decoders, exact to the vendored status-v5.schema.json
|
|
1272
|
+
// and correction-plan-request.schema.json on gentle-ai main.
|
|
1273
|
+
|
|
1274
|
+
function decodeProviderTask(value , label ) {
|
|
1275
|
+
const task = exactRecord(value, label, ["agent", "role", "prompt"]);
|
|
1276
|
+
return {
|
|
1277
|
+
agent: enumeration(task.agent, ["review-refuter", "review-validator"] , `${label}.agent`),
|
|
1278
|
+
role: enumeration(task.role, ["refuter", "targeted-validator"] , `${label}.role`),
|
|
1279
|
+
prompt: nonempty(task.prompt, `${label}.prompt`),
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function decodeSubmissionDescriptorV5(value , label , operationToken ) {
|
|
1284
|
+
if (operationToken === "finalize") {
|
|
1285
|
+
const descriptor = exactRecord(value, label, ["operation_token", "argument_tokens", "value"]);
|
|
1286
|
+
const argumentTokens = stringArray(descriptor.argument_tokens, `${label}.argument_tokens`, { minimum: 7 });
|
|
1287
|
+
const source = exactRecord(descriptor.value, `${label}.value`, ["slot", "domain", "substitution_location"], ["schema", "minimum", "maximum"]);
|
|
1288
|
+
const slot = enumeration(source.slot, ["correction_lines", "validation"] , `${label}.value.slot`);
|
|
1289
|
+
return {
|
|
1290
|
+
operationToken,
|
|
1291
|
+
argumentTokens,
|
|
1292
|
+
value: {
|
|
1293
|
+
slot,
|
|
1294
|
+
domain: nonempty(source.domain, `${label}.value.domain`),
|
|
1295
|
+
substitutionLocation: integer(source.substitution_location, `${label}.value.substitution_location`, 0, argumentTokens.length - 1),
|
|
1296
|
+
...(source.schema === undefined ? {} : { schema: nonempty(source.schema, `${label}.value.schema`) }),
|
|
1297
|
+
...(source.minimum === undefined ? {} : { minimum: integer(source.minimum, `${label}.value.minimum`, 1, 200) }),
|
|
1298
|
+
...(source.maximum === undefined ? {} : { maximum: integer(source.maximum, `${label}.value.maximum`, 1, 200) }),
|
|
1299
|
+
},
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
const descriptor = exactRecord(value, label, ["operation_token", "argument_tokens", "values"]);
|
|
1303
|
+
const argumentTokens = stringArray(descriptor.argument_tokens, `${label}.argument_tokens`, { minimum: 6, maximum: 6 });
|
|
1304
|
+
const values = array(descriptor.values, `${label}.values`, (entry, entryLabel) => {
|
|
1305
|
+
const source = exactRecord(entry, entryLabel, ["slot", "domain", "substitution_location"], ["schema", "allowed_values"]);
|
|
1306
|
+
return {
|
|
1307
|
+
slot: nonempty(source.slot, `${entryLabel}.slot`),
|
|
1308
|
+
domain: nonempty(source.domain, `${entryLabel}.domain`),
|
|
1309
|
+
substitutionLocation: integer(source.substitution_location, `${entryLabel}.substitution_location`, 0, argumentTokens.length - 1),
|
|
1310
|
+
...(source.schema === undefined ? {} : { schema: nonempty(source.schema, `${entryLabel}.schema`) }),
|
|
1311
|
+
...(source.allowed_values === undefined ? {} : { allowedValues: stringArray(source.allowed_values, `${entryLabel}.allowed_values`, { minimum: 1, unique: true }) }),
|
|
1312
|
+
};
|
|
1313
|
+
}, { minimum: 2, maximum: 2 });
|
|
1314
|
+
return { operationToken, argumentTokens, values };
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function decodeCorrectionPlanRequestV1(value , label ) {
|
|
1318
|
+
const body = exactRecord(value, label, ["schema", "request_hash", "lineage_id", "expected_revision", "target_identity", "correction_budget", "fix_finding_ids", "findings"]);
|
|
1319
|
+
if (body.schema !== "gentle-ai.review-correction-plan-request/v1") throw new TypeError(`${label}.schema must be gentle-ai.review-correction-plan-request/v1`);
|
|
1320
|
+
return {
|
|
1321
|
+
schema: "gentle-ai.review-correction-plan-request/v1",
|
|
1322
|
+
requestHash: sha256(body.request_hash, `${label}.request_hash`),
|
|
1323
|
+
lineageId: lineage(body.lineage_id, `${label}.lineage_id`),
|
|
1324
|
+
expectedRevision: sha256(body.expected_revision, `${label}.expected_revision`),
|
|
1325
|
+
targetIdentity: sha256(body.target_identity, `${label}.target_identity`),
|
|
1326
|
+
correctionBudget: integer(body.correction_budget, `${label}.correction_budget`, 1, 200),
|
|
1327
|
+
fixFindingIds: stringArray(body.fix_finding_ids, `${label}.fix_finding_ids`, { minimum: 1, unique: true }),
|
|
1328
|
+
findings: array(body.findings, `${label}.findings`, (entry, entryLabel) => {
|
|
1329
|
+
const finding = exactRecord(entry, entryLabel, ["id", "lens", "location", "severity", "claim", "proof_refs", "evidence", "evidence_class", "causal_disposition"]);
|
|
1330
|
+
return {
|
|
1331
|
+
id: nonempty(finding.id, `${entryLabel}.id`),
|
|
1332
|
+
lens: enumeration(finding.lens, ["risk", "resilience", "readability", "reliability"] , `${entryLabel}.lens`),
|
|
1333
|
+
location: nonempty(finding.location, `${entryLabel}.location`),
|
|
1334
|
+
severity: enumeration(finding.severity, ["BLOCKER", "CRITICAL"] , `${entryLabel}.severity`),
|
|
1335
|
+
claim: nonempty(finding.claim, `${entryLabel}.claim`),
|
|
1336
|
+
proofRefs: stringArray(finding.proof_refs, `${entryLabel}.proof_refs`, { minimum: 1 }),
|
|
1337
|
+
evidence: nonempty(finding.evidence, `${entryLabel}.evidence`),
|
|
1338
|
+
evidenceClass: enumeration(finding.evidence_class, ["deterministic", "inferential"] , `${entryLabel}.evidence_class`),
|
|
1339
|
+
causalDisposition: enumeration(finding.causal_disposition, ["introduced", "behavior-activated", "worsened"] , `${entryLabel}.causal_disposition`),
|
|
1340
|
+
};
|
|
1341
|
+
}, { minimum: 1 }),
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
export function decodeReviewForecastV1(value , label = "status.forecast") {
|
|
1346
|
+
const body = exactRecord(value, label, ["horizon", "steps"]);
|
|
1347
|
+
const horizon = enumeration(body.horizon, ["partial", "terminal"] , `${label}.horizon`);
|
|
1348
|
+
const steps = array(body.steps, `${label}.steps`, (entry, entryLabel) => {
|
|
1349
|
+
const step = exactRecord(entry, entryLabel, ["step", "kind", "reason_code", "description"]);
|
|
1350
|
+
if (step.step !== 1) throw new TypeError(`${entryLabel}.step must be 1`);
|
|
1351
|
+
return {
|
|
1352
|
+
step: 1 ,
|
|
1353
|
+
kind: enumeration(step.kind, ["execute", "collect", "stop"] , `${entryLabel}.kind`),
|
|
1354
|
+
reasonCode: text(step.reason_code, `${entryLabel}.reason_code`, { minimum: 1, pattern: /^[a-z0-9_]+$/ }),
|
|
1355
|
+
description: nonempty(step.description, `${entryLabel}.description`),
|
|
1356
|
+
};
|
|
1357
|
+
}, { minimum: 1, maximum: 1 });
|
|
1358
|
+
// The published horizon-to-step invariant: a stop head is terminal, any
|
|
1359
|
+
// other head is partial.
|
|
1360
|
+
if ((horizon === "terminal") !== (steps[0] .kind === "stop")) throw new TypeError(`${label}.horizon does not match its step kind`);
|
|
1361
|
+
return { horizon, steps };
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// The two v5 reason codes whose transitions must carry the correction plan
|
|
1365
|
+
// request; every other reason code must not.
|
|
1366
|
+
const CORRECTION_REQUEST_REASON_CODES = Object.freeze(["correction_plan_required", "corrected_candidate_unavailable"] );
|
|
1367
|
+
// v5 capture operations that must carry a submission descriptor.
|
|
1368
|
+
const V5_SUBMISSION_CAPTURE_OPERATIONS = Object.freeze(["external.plan_correction", "external.run_targeted_validation", "review.capture-evidence"] );
|
|
1369
|
+
|
|
1370
|
+
function decodeCollectInput(value , label , v5 ) {
|
|
1371
|
+
const input = exactRecord(value, label, ["name", "schema", "capture_operation", "arguments"], ["artifact_subject", "base_tree", "candidate_tree", "changed_path_manifest", "validation_request", "submission", ...(v5 ? ["provider_task"] : [])]);
|
|
1048
1372
|
const name = text(input.name, `${label}.name`, { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
1049
1373
|
const schema = nonempty(input.schema, `${label}.schema`);
|
|
1050
1374
|
const captureOperation = nonempty(input.capture_operation, `${label}.capture_operation`);
|
|
@@ -1053,8 +1377,43 @@ function decodeCollectInput(value , label )
|
|
|
1053
1377
|
if (captureOperation === "external.run_targeted_validation") {
|
|
1054
1378
|
if (input.validation_request === undefined) throw new TypeError(`${label}.validation_request is required`);
|
|
1055
1379
|
if (schema !== "gentle-ai.review-targeted-validation-request/v1") throw new TypeError(`${label}.schema must be gentle-ai.review-targeted-validation-request/v1`);
|
|
1380
|
+
} else if (captureOperation === REVIEW_PROVIDER_ROLE_CAPTURE_OPERATION.CAPTURE_VALIDATION) {
|
|
1381
|
+
if (input.validation_request === undefined) throw new TypeError(`${label}.validation_request is required`);
|
|
1056
1382
|
} else if (input.validation_request !== undefined) {
|
|
1057
|
-
throw new TypeError(`${label}.validation_request is only valid for external.run_targeted_validation`);
|
|
1383
|
+
throw new TypeError(`${label}.validation_request is only valid for external.run_targeted_validation or review.capture-validation`);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// v5-only surfaces (vendored status-v5.schema.json): the provider role task
|
|
1387
|
+
// rides exactly the external.run_provider_role vector, and the finalize/
|
|
1388
|
+
// capture-evidence submission descriptor forms ride their own vectors.
|
|
1389
|
+
if (v5) {
|
|
1390
|
+
if (captureOperation === "external.run_provider_role") {
|
|
1391
|
+
if (input.provider_task === undefined) throw new TypeError(`${label}.provider_task is required for external.run_provider_role`);
|
|
1392
|
+
} else if (input.provider_task !== undefined) {
|
|
1393
|
+
throw new TypeError(`${label}.provider_task is only valid for external.run_provider_role`);
|
|
1394
|
+
}
|
|
1395
|
+
if ((V5_SUBMISSION_CAPTURE_OPERATIONS ).includes(captureOperation)) {
|
|
1396
|
+
if (input.submission === undefined) throw new TypeError(`${label}.submission is required for ${captureOperation}`);
|
|
1397
|
+
if (captureOperation === "external.plan_correction" && schema !== "gentle-ai.review-correction-plan/v1") throw new TypeError(`${label}.schema must be gentle-ai.review-correction-plan/v1`);
|
|
1398
|
+
if (captureOperation === "review.capture-evidence" && schema !== "https://gentle-ai.dev/schema/review/verification-evidence/v1") throw new TypeError(`${label}.schema must be https://gentle-ai.dev/schema/review/verification-evidence/v1`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// gentle-pi#311 P4-roles: the two Go-owned non-lens provider role capture
|
|
1403
|
+
// operations render SELF-CONTAINED authority-advancing vectors. Executing
|
|
1404
|
+
// the exact rendered tokens makes Go materialize the role prompt, run its
|
|
1405
|
+
// own locked-down pi subprocess, and admit the raw verdict — so a
|
|
1406
|
+
// submission descriptor (the host-mediated completing form) on one of
|
|
1407
|
+
// these inputs would hand the caller a way to author the verdict and is
|
|
1408
|
+
// rejected as a provider contract violation.
|
|
1409
|
+
if (captureOperation === REVIEW_PROVIDER_ROLE_CAPTURE_OPERATION.CAPTURE_REFUTER && schema !== "https://gentle-ai.dev/schema/review/refuter/v1") {
|
|
1410
|
+
throw new TypeError(`${label}.schema must be https://gentle-ai.dev/schema/review/refuter/v1`);
|
|
1411
|
+
}
|
|
1412
|
+
if (captureOperation === REVIEW_PROVIDER_ROLE_CAPTURE_OPERATION.CAPTURE_VALIDATION && schema !== "https://gentle-ai.dev/schema/review/validator/v1") {
|
|
1413
|
+
throw new TypeError(`${label}.schema must be https://gentle-ai.dev/schema/review/validator/v1`);
|
|
1414
|
+
}
|
|
1415
|
+
if (input.submission !== undefined && (REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS ).includes(captureOperation)) {
|
|
1416
|
+
throw new TypeError(`${label}.submission is not allowed on the self-contained ${captureOperation} vector`);
|
|
1058
1417
|
}
|
|
1059
1418
|
|
|
1060
1419
|
if (captureOperation === "review.capture-result") {
|
|
@@ -1065,6 +1424,23 @@ function decodeCollectInput(value , label )
|
|
|
1065
1424
|
} else if (input.artifact_subject !== undefined || input.base_tree !== undefined || input.candidate_tree !== undefined || input.changed_path_manifest !== undefined) {
|
|
1066
1425
|
throw new TypeError(`${label} carries capture-result fields without review.capture-result`);
|
|
1067
1426
|
}
|
|
1427
|
+
const submissionOperations = v5 ? ["review.capture-result", ...V5_SUBMISSION_CAPTURE_OPERATIONS] : ["review.capture-result"];
|
|
1428
|
+
if (input.submission !== undefined && !submissionOperations.includes(captureOperation)) {
|
|
1429
|
+
throw new TypeError(v5 ? `${label}.submission is only valid for ${submissionOperations.join(", ")}` : `${label}.submission is only valid for review.capture-result`);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// The v5 descriptor forms are discriminated by their closed operation
|
|
1433
|
+
// tokens; every other submission stays the host-relay completing form.
|
|
1434
|
+
let submission ;
|
|
1435
|
+
let submissionDescriptor ;
|
|
1436
|
+
if (input.submission !== undefined) {
|
|
1437
|
+
const operationToken = (input.submission )?.operation_token;
|
|
1438
|
+
if (v5 && (operationToken === "finalize" || operationToken === "capture-evidence")) {
|
|
1439
|
+
submissionDescriptor = decodeSubmissionDescriptorV5(input.submission, `${label}.submission`, operationToken);
|
|
1440
|
+
} else {
|
|
1441
|
+
submission = decodeCaptureSubmission(input.submission, `${label}.submission`, v5);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1068
1444
|
|
|
1069
1445
|
return {
|
|
1070
1446
|
name,
|
|
@@ -1076,14 +1452,28 @@ function decodeCollectInput(value , label )
|
|
|
1076
1452
|
...(input.candidate_tree === undefined ? {} : { candidateTree: gitTree(input.candidate_tree, `${label}.candidate_tree`) }),
|
|
1077
1453
|
...(input.changed_path_manifest === undefined ? {} : { changedPathManifest: array(input.changed_path_manifest, `${label}.changed_path_manifest`, decodeChangedPathEntry, { unique: true }) }),
|
|
1078
1454
|
...(input.validation_request === undefined ? {} : { validationRequest: decodeTargetedValidationRequestV1(input.validation_request, `${label}.validation_request`) }),
|
|
1455
|
+
...(submission === undefined ? {} : { submission }),
|
|
1456
|
+
...(submissionDescriptor === undefined ? {} : { submissionDescriptor }),
|
|
1457
|
+
...(v5 && input.provider_task !== undefined ? { providerTask: decodeProviderTask(input.provider_task, `${label}.provider_task`) } : {}),
|
|
1079
1458
|
};
|
|
1080
1459
|
}
|
|
1081
1460
|
|
|
1082
|
-
export function decodeReviewNextTransitionV3(value ) {
|
|
1083
|
-
const
|
|
1461
|
+
export function decodeReviewNextTransitionV3(value , options = {}) {
|
|
1462
|
+
const v5 = options.v5 === true;
|
|
1463
|
+
const transition = exactRecord(value, "next_transition", ["kind", "reason_code"], ["execute", "collect", ...(v5 ? ["correction_request"] : [])]);
|
|
1084
1464
|
const kind = enumeration(transition.kind, ["execute", "collect", "stop"] , "next_transition.kind");
|
|
1085
1465
|
const reasonCode = text(transition.reason_code, "next_transition.reason_code", { minimum: 1, pattern: /^[a-z0-9_]+$/ });
|
|
1086
1466
|
|
|
1467
|
+
// status/v5: the bounded correction plan request rides exactly its two
|
|
1468
|
+
// reason codes and never any other (vendored status-v5.schema.json).
|
|
1469
|
+
let correctionRequest ;
|
|
1470
|
+
if (v5) {
|
|
1471
|
+
const required = (CORRECTION_REQUEST_REASON_CODES ).includes(reasonCode);
|
|
1472
|
+
if (required && transition.correction_request === undefined) throw new TypeError(`next_transition.correction_request is required for ${reasonCode}`);
|
|
1473
|
+
if (!required && transition.correction_request !== undefined) throw new TypeError(`next_transition.correction_request is only valid for ${CORRECTION_REQUEST_REASON_CODES.join(", ")}`);
|
|
1474
|
+
if (transition.correction_request !== undefined) correctionRequest = decodeCorrectionPlanRequestV1(transition.correction_request, "next_transition.correction_request");
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1087
1477
|
if (kind === "execute") {
|
|
1088
1478
|
// `command` is an optional, ready-to-paste rendering of `arguments` (the
|
|
1089
1479
|
// exact same binding as a single shell-ready string) — observed live
|
|
@@ -1110,16 +1500,16 @@ export function decodeReviewNextTransitionV3(value )
|
|
|
1110
1500
|
const revision = binding.revision === undefined ? undefined : sha256(binding.revision, "next_transition.execute.binding.revision");
|
|
1111
1501
|
const command = execute.command === undefined ? undefined : nonempty(execute.command, "next_transition.execute.command");
|
|
1112
1502
|
if (transition.collect !== undefined) throw new TypeError("next_transition.collect is incompatible with execute");
|
|
1113
|
-
return { kind, reasonCode, execute: { operation, arguments: argumentsList, preconditions, binding: { targetIdentity, ...(lineageId === undefined ? {} : { lineageId }), ...(revision === undefined ? {} : { revision }) }, ...(command === undefined ? {} : { command }) } };
|
|
1503
|
+
return { kind, reasonCode, execute: { operation, arguments: argumentsList, preconditions, binding: { targetIdentity, ...(lineageId === undefined ? {} : { lineageId }), ...(revision === undefined ? {} : { revision }) }, ...(command === undefined ? {} : { command }) }, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
|
|
1114
1504
|
}
|
|
1115
1505
|
if (kind === "collect") {
|
|
1116
1506
|
const collect = exactRecord(transition.collect, "next_transition.collect", ["inputs"]);
|
|
1117
|
-
const inputs = array(collect.inputs, "next_transition.collect.inputs", (entry, label) => decodeCollectInput(entry, label), { minimum: 1 });
|
|
1507
|
+
const inputs = array(collect.inputs, "next_transition.collect.inputs", (entry, label) => decodeCollectInput(entry, label, v5), { minimum: 1 });
|
|
1118
1508
|
if (transition.execute !== undefined) throw new TypeError("next_transition.execute is incompatible with collect");
|
|
1119
|
-
return { kind, reasonCode, collect: { inputs } };
|
|
1509
|
+
return { kind, reasonCode, collect: { inputs }, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
|
|
1120
1510
|
}
|
|
1121
1511
|
if (transition.execute !== undefined || transition.collect !== undefined) throw new TypeError("next_transition stop cannot carry a transition");
|
|
1122
|
-
return { kind, reasonCode };
|
|
1512
|
+
return { kind, reasonCode, ...(correctionRequest === undefined ? {} : { correctionRequest }) };
|
|
1123
1513
|
}
|
|
1124
1514
|
|
|
1125
1515
|
// ---------------------------------------------------------------------------
|
|
@@ -1179,10 +1569,15 @@ function decodeFinalVerificationRetry(value , label )
|
|
|
1179
1569
|
// ---------------------------------------------------------------------------
|
|
1180
1570
|
|
|
1181
1571
|
export function decodeReviewStatusV3(value ) {
|
|
1572
|
+
// Additive forward acceptance: status/v5 (gentle-ai main; ground-truthed
|
|
1573
|
+
// against a live capture and the vendored status-v5.schema.json) is the v3
|
|
1574
|
+
// key set plus the optional forecast and the v5-only next_transition
|
|
1575
|
+
// surfaces. The v3 identity keeps rejecting every v5-only field.
|
|
1576
|
+
const v5 = typeof value === "object" && value !== null && (value ).schema === "gentle-ai.review-integration.status/v5";
|
|
1182
1577
|
const body = exactRecord(value, "status", [
|
|
1183
1578
|
"schema", "contract", "operation", "applicability", "receipt", "action", "replayability", "target_identity", "projection", "repair", "candidates",
|
|
1184
|
-
], ["authority", "frozen", "reconciliation", "action_disposition", "eligibility", "next_transition", "authority_target_identity", "validation_request", "final_verification_retry"]);
|
|
1185
|
-
requireIdentity(body, "gentle-ai.review-integration.status/v3", REVIEW_INTEGRATION_OPERATION.STATUS);
|
|
1579
|
+
], ["authority", "frozen", "reconciliation", "action_disposition", "eligibility", "next_transition", "authority_target_identity", "validation_request", "final_verification_retry", ...(v5 ? ["forecast", "repository_context"] : [])]);
|
|
1580
|
+
requireIdentity(body, v5 ? "gentle-ai.review-integration.status/v5" : "gentle-ai.review-integration.status/v3", REVIEW_INTEGRATION_OPERATION.STATUS);
|
|
1186
1581
|
|
|
1187
1582
|
const applicability = enumeration(body.applicability, ["current_target", "unrelated", "ambiguous", "corrupted"] , "status.applicability");
|
|
1188
1583
|
const receiptBody = exactRecord(body.receipt, "status.receipt", ["status"], ["identity"]);
|
|
@@ -1223,7 +1618,11 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1223
1618
|
if ((action === "recover" || action === "retry_final_verification") && actionDisposition === undefined) throw new TypeError(`${action} status requires action_disposition`);
|
|
1224
1619
|
if (action !== "recover" && action !== "retry_final_verification" && actionDisposition !== undefined) throw new TypeError("status.action_disposition is only valid for the recover or retry_final_verification action");
|
|
1225
1620
|
if (body.eligibility !== undefined) decodeEligibility(body.eligibility, "status.eligibility");
|
|
1226
|
-
const nextTransition = body.next_transition === undefined ? undefined : decodeReviewNextTransitionV3(body.next_transition);
|
|
1621
|
+
const nextTransition = body.next_transition === undefined ? undefined : decodeReviewNextTransitionV3(body.next_transition, { v5 });
|
|
1622
|
+
// status/v5 dependentRequired: a forecast previews the transition head, so
|
|
1623
|
+
// it can only accompany an actual next_transition.
|
|
1624
|
+
const forecast = v5 && body.forecast !== undefined ? decodeReviewForecastV1(body.forecast) : undefined;
|
|
1625
|
+
if (forecast !== undefined && nextTransition === undefined) throw new TypeError("status.forecast requires status.next_transition");
|
|
1227
1626
|
const replayability = enumeration(body.replayability, Object.values(REVIEW_REPLAYABILITY), "status.replayability");
|
|
1228
1627
|
|
|
1229
1628
|
let reconciliation ;
|
|
@@ -1258,6 +1657,24 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1258
1657
|
throw new TypeError("status.final_verification_retry is only valid for the retry_final_verification action");
|
|
1259
1658
|
}
|
|
1260
1659
|
|
|
1660
|
+
// status/v5 additive top-level repository context reference (captured
|
|
1661
|
+
// 2026-08-16 from 2.4.0-main.b1afef46; missing from the published
|
|
1662
|
+
// status-v5.schema.json, so the live capture is authoritative). Same
|
|
1663
|
+
// reference shape as start/v3's repository_context.
|
|
1664
|
+
let repositoryContext ;
|
|
1665
|
+
if (body.repository_context !== undefined) {
|
|
1666
|
+
const source = exactRecord(body.repository_context, "status.repository_context", ["capability", "handle", "revision", "target_identity"], ["event_id", "outcome"]);
|
|
1667
|
+
if (source.capability !== "review.opaque_repository_context") throw new TypeError("status.repository_context.capability is unsupported");
|
|
1668
|
+
repositoryContext = {
|
|
1669
|
+
capability: "review.opaque_repository_context",
|
|
1670
|
+
handle: text(source.handle, "status.repository_context.handle", { pattern: /^rctx1_[0-9a-f]{64}$/ }),
|
|
1671
|
+
revision: sha256(source.revision, "status.repository_context.revision"),
|
|
1672
|
+
targetIdentity: sha256(source.target_identity, "status.repository_context.target_identity"),
|
|
1673
|
+
...(source.event_id === undefined ? {} : { eventId: sha256(source.event_id, "status.repository_context.event_id") }),
|
|
1674
|
+
...(source.outcome === undefined ? {} : { outcome: enumeration(source.outcome, REPOSITORY_CONTEXT_OUTCOMES, "status.repository_context.outcome") }),
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1261
1678
|
return {
|
|
1262
1679
|
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
1263
1680
|
applicability,
|
|
@@ -1276,6 +1693,8 @@ export function decodeReviewStatusV3(value ) {
|
|
|
1276
1693
|
...(nextTransition === undefined ? {} : { nextTransition }),
|
|
1277
1694
|
...(validationRequest === undefined ? {} : { validationRequest }),
|
|
1278
1695
|
...(finalVerificationRetry === undefined ? {} : { finalVerificationRetry }),
|
|
1696
|
+
...(forecast === undefined ? {} : { forecast }),
|
|
1697
|
+
...(repositoryContext === undefined ? {} : { repositoryContext }),
|
|
1279
1698
|
raw: body,
|
|
1280
1699
|
};
|
|
1281
1700
|
}
|
|
@@ -1296,11 +1715,10 @@ function decodeConsentChoice(value , label , answer
|
|
|
1296
1715
|
};
|
|
1297
1716
|
}
|
|
1298
1717
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
requireIdentity(body, "gentle-ai.review-integration.consent/v2", "review.start");
|
|
1718
|
+
// The semantic surface shared verbatim by both accepted consent identities.
|
|
1719
|
+
// Every label and guard predates consent/v3, so the v2 decode stays
|
|
1720
|
+
// byte-identical (test-locked) while v3 adds only its own identity gate.
|
|
1721
|
+
function decodeConsentSemantics(body ) {
|
|
1304
1722
|
if (body.action !== "consent_required") throw new TypeError("consent.action must be consent_required");
|
|
1305
1723
|
if (body.blocking !== true) throw new TypeError("consent.blocking must be true");
|
|
1306
1724
|
|
|
@@ -1317,7 +1735,6 @@ export function decodeReviewConsentV2(value ) {
|
|
|
1317
1735
|
if (offPathSource.command !== "gentle-ai review mode disable") throw new TypeError("consent.off_path.command is unsupported");
|
|
1318
1736
|
|
|
1319
1737
|
return {
|
|
1320
|
-
schema: "gentle-ai.review-integration.consent/v2",
|
|
1321
1738
|
contract: REVIEW_INTEGRATION_CONTRACT,
|
|
1322
1739
|
operation: "review.start",
|
|
1323
1740
|
action: "consent_required",
|
|
@@ -1333,6 +1750,39 @@ export function decodeReviewConsentV2(value ) {
|
|
|
1333
1750
|
riskEvidence: stringArray(body.risk_evidence, "consent.risk_evidence"),
|
|
1334
1751
|
choices: [granted, declined],
|
|
1335
1752
|
offPath: { note: nonempty(offPathSource.note, "consent.off_path.note"), command: "gentle-ai review mode disable" },
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
const CONSENT_KEYS_V2 = Object.freeze([
|
|
1757
|
+
"schema", "contract", "operation", "action", "blocking", "target_identity", "projection", "risk_level", "changed_files", "changed_lines", "headline", "reason", "value", "risk_evidence", "choices", "off_path",
|
|
1758
|
+
] );
|
|
1759
|
+
|
|
1760
|
+
export function decodeReviewConsentV2(value ) {
|
|
1761
|
+
const body = exactRecord(value, "consent", CONSENT_KEYS_V2);
|
|
1762
|
+
requireIdentity(body, "gentle-ai.review-integration.consent/v2", "review.start");
|
|
1763
|
+
return {
|
|
1764
|
+
schema: "gentle-ai.review-integration.consent/v2",
|
|
1765
|
+
...decodeConsentSemantics(body),
|
|
1766
|
+
raw: body,
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// consent/v3 (gentle-ai >= 2.3.0): the v2 surface plus the required, fixed
|
|
1771
|
+
// `agent` runtime binding. Ground truth is the captured envelope from a
|
|
1772
|
+
// 2.4.0-main binary (tests/fixtures/devbinary/consent-v3.captured.json) plus
|
|
1773
|
+
// gentle-ai main contracts/review-integration/v2/schemas/consent-v3.schema.json.
|
|
1774
|
+
// The choice-invocation shape deliberately stays the shared v2 pattern: the
|
|
1775
|
+
// published v3 schema demands an `--agent claude-code` token that the live
|
|
1776
|
+
// emitter omits when the caller declared no --agent, so the capture is
|
|
1777
|
+
// authoritative and Pi replays whichever provider-owned invocation arrived.
|
|
1778
|
+
export function decodeReviewConsentV3(value ) {
|
|
1779
|
+
const body = exactRecord(value, "consent", [...CONSENT_KEYS_V2, "agent"]);
|
|
1780
|
+
requireIdentity(body, "gentle-ai.review-integration.consent/v3", "review.start");
|
|
1781
|
+
if (body.agent !== "claude-code") throw new TypeError("consent.agent must be claude-code");
|
|
1782
|
+
return {
|
|
1783
|
+
schema: "gentle-ai.review-integration.consent/v3",
|
|
1784
|
+
agent: "claude-code",
|
|
1785
|
+
...decodeConsentSemantics(body),
|
|
1336
1786
|
raw: body,
|
|
1337
1787
|
};
|
|
1338
1788
|
}
|
|
@@ -1396,7 +1846,7 @@ export function decodeReviewFailureV2(value ) {
|
|
|
1396
1846
|
"schema", "contract", "operation", "phase", "code", "message", "mutation_outcome", "authority_applicability", "retry_safe", "replayability", "required_inputs", "next_action",
|
|
1397
1847
|
], ["lineage_id", "request_digest", "progress_identity", "cause_category", "cause", "context"]);
|
|
1398
1848
|
requireIdentity(body, "gentle-ai.review-integration.failure/v2");
|
|
1399
|
-
const operation = enumeration(body.operation,
|
|
1849
|
+
const operation = enumeration(body.operation, FAILURE_OPERATIONS, "failure.operation");
|
|
1400
1850
|
|
|
1401
1851
|
if (body.progress_identity !== undefined) {
|
|
1402
1852
|
if (body.lineage_id === undefined || body.request_digest === undefined) throw new TypeError("failure.progress_identity requires lineage_id and request_digest");
|
|
@@ -1566,3 +2016,54 @@ export function decodeReviewRepairV2(value ) {
|
|
|
1566
2016
|
raw: body,
|
|
1567
2017
|
};
|
|
1568
2018
|
}
|
|
2019
|
+
|
|
2020
|
+
// ---------------------------------------------------------------------------
|
|
2021
|
+
// result-artifact/v2 — the `review capture-result` admission answer
|
|
2022
|
+
// ---------------------------------------------------------------------------
|
|
2023
|
+
|
|
2024
|
+
// The provider's admitted-reviewer-result envelope, printed by `review
|
|
2025
|
+
// capture-result` when a reviewer result is admitted and re-discovered by
|
|
2026
|
+
// STATUS artifact discovery. Unlike the negotiated envelopes above it carries
|
|
2027
|
+
// no `contract`/`operation` identity pair — the schema constant plus the
|
|
2028
|
+
// capability constant are its complete identity (vendored ground truth:
|
|
2029
|
+
// contracts/review-integration/v1/schemas/result-artifact-v2.schema.json,
|
|
2030
|
+
// confirmed against a live 2.4.0-main capture; the v2.2.3 pinned emitter
|
|
2031
|
+
// shares the exact same struct). Exactly one locator is present: a
|
|
2032
|
+
// provider-owned store `path`, or the opaque `rart1_` `reference` minted for
|
|
2033
|
+
// repository-context captures.
|
|
2034
|
+
|
|
2035
|
+
|
|
2036
|
+
|
|
2037
|
+
|
|
2038
|
+
|
|
2039
|
+
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
|
|
2044
|
+
|
|
2045
|
+
|
|
2046
|
+
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
export function decodeReviewResultArtifactV2(value ) {
|
|
2050
|
+
const body = exactRecord(value, "result_artifact", ["schema", "capability", "sha256", "lineage_id", "target_identity", "lens", "selected_order", "subject_hash", "admission_decision"], ["path", "reference"]);
|
|
2051
|
+
if (body.schema !== "gentle-ai.review-result-artifact/v2") throw new TypeError("result_artifact.schema must be gentle-ai.review-result-artifact/v2");
|
|
2052
|
+
if (body.capability !== "review.native_result_artifact") throw new TypeError("result_artifact.capability must be review.native_result_artifact");
|
|
2053
|
+
if (body.admission_decision !== "completed") throw new TypeError("result_artifact.admission_decision must be completed");
|
|
2054
|
+
if ((body.path === undefined) === (body.reference === undefined)) throw new TypeError("result_artifact must carry exactly one of path or reference");
|
|
2055
|
+
return {
|
|
2056
|
+
schema: "gentle-ai.review-result-artifact/v2",
|
|
2057
|
+
capability: "review.native_result_artifact",
|
|
2058
|
+
sha256: sha256(body.sha256, "result_artifact.sha256"),
|
|
2059
|
+
lineageId: lineage(body.lineage_id, "result_artifact.lineage_id"),
|
|
2060
|
+
targetIdentity: sha256(body.target_identity, "result_artifact.target_identity"),
|
|
2061
|
+
lens: enumeration(body.lens, REVIEW_LENSES, "result_artifact.lens"),
|
|
2062
|
+
selectedOrder: integer(body.selected_order, "result_artifact.selected_order", 0, 3),
|
|
2063
|
+
subjectHash: sha256(body.subject_hash, "result_artifact.subject_hash"),
|
|
2064
|
+
admissionDecision: "completed",
|
|
2065
|
+
...(body.path === undefined ? {} : { path: nonempty(body.path, "result_artifact.path") }),
|
|
2066
|
+
...(body.reference === undefined ? {} : { reference: text(body.reference, "result_artifact.reference", { pattern: /^rart1_[0-9a-f]{64}$/ }) }),
|
|
2067
|
+
raw: body,
|
|
2068
|
+
};
|
|
2069
|
+
}
|