@dzhechkov/skills-bto 1.1.1 → 1.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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/skills-bto",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Build-Test-Optimize skill pack for Claude Code — structured BTO pipeline with quality gates,
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Build-Test-Optimize skill pack for Claude Code — structured BTO pipeline with quality gates, witness chain verification, judge attestation, and optimization workflows",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"skills-bto": "./bin/cli.js"
|
|
@@ -39,5 +39,13 @@
|
|
|
39
39
|
"homepage": "https://github.com/dzhechko/product-keysarium-2026#readme",
|
|
40
40
|
"bugs": {
|
|
41
41
|
"url": "https://github.com/dzhechko/product-keysarium-2026/issues"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@dzhechkov/keysarium-core": "^1.0.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"@dzhechkov/keysarium-core": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
42
50
|
}
|
|
43
51
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# /verify-chain — Witness Chain and Judge Attestation Verification
|
|
2
|
+
|
|
3
|
+
> Verify the integrity of SHA-256 witness chains and BTO judge attestation chains.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/verify-chain [path] — Verify a specific research directory
|
|
9
|
+
/verify-chain all — Verify all research directories
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Arguments
|
|
13
|
+
|
|
14
|
+
- `path`: Path to a research directory (e.g., `researches/bank_kc_automation/`) or `"all"` to scan all directories under `researches/`.
|
|
15
|
+
|
|
16
|
+
$ARGUMENTS
|
|
17
|
+
|
|
18
|
+
## Protocol
|
|
19
|
+
|
|
20
|
+
### Step 1: Parse Arguments and Discover Targets
|
|
21
|
+
|
|
22
|
+
Determine which directories to verify:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
TARGET="$ARGUMENTS"
|
|
26
|
+
|
|
27
|
+
if [ "${TARGET}" = "all" ]; then
|
|
28
|
+
# Find all research directories
|
|
29
|
+
DIRS=$(find researches/ -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
|
|
30
|
+
if [ -z "${DIRS}" ]; then
|
|
31
|
+
echo "No research directories found under researches/"
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
else
|
|
35
|
+
# Single directory
|
|
36
|
+
DIRS="${TARGET}"
|
|
37
|
+
if [ ! -d "${TARGET}" ]; then
|
|
38
|
+
echo "ERROR: Directory '${TARGET}' not found"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Step 2: Load Protocols
|
|
45
|
+
|
|
46
|
+
Read the witness chain and judge attestation protocols for reference:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Read: lib/witness-chain.md
|
|
50
|
+
Read: lib/judge-attestation.md
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Step 3: Verify Each Directory
|
|
54
|
+
|
|
55
|
+
For each target directory, perform two verification passes:
|
|
56
|
+
|
|
57
|
+
#### Pass A: Witness Chain Verification
|
|
58
|
+
|
|
59
|
+
1. Check if `.witness-chain.json` exists in the directory
|
|
60
|
+
2. If not found: report `NOT_FOUND` for this directory (not an error — chain may not have been enabled)
|
|
61
|
+
3. If found:
|
|
62
|
+
a. Parse the JSON file
|
|
63
|
+
b. Detect platform SHA-256 command (`sha256sum` or `shasum -a 256`)
|
|
64
|
+
c. For each record in the chain:
|
|
65
|
+
- Read the artifact file
|
|
66
|
+
- If file missing: record FAIL with "artifact file not found"
|
|
67
|
+
- Compute the chained hash: `SHA-256(file_content + previous_hash)`
|
|
68
|
+
- For sequence 0: previous_hash = NULL_HASH (64 zeros)
|
|
69
|
+
- For sequence N: previous_hash = chain[N-1].hash (without "sha256:" prefix)
|
|
70
|
+
- Compare computed hash with stored hash
|
|
71
|
+
- If match: record PASS
|
|
72
|
+
- If mismatch: record FAIL with expected vs actual hash
|
|
73
|
+
d. Verify sequence numbers are contiguous (0, 1, 2, ...)
|
|
74
|
+
e. Verify timestamps are chronologically ordered
|
|
75
|
+
|
|
76
|
+
#### Pass B: Judge Attestation Verification
|
|
77
|
+
|
|
78
|
+
1. Check if `.judge-attestations.json` exists in the directory
|
|
79
|
+
2. If not found: skip (attestations are optional — only present if BTO was run)
|
|
80
|
+
3. If found:
|
|
81
|
+
a. Parse the JSON file
|
|
82
|
+
b. For each evaluation round:
|
|
83
|
+
- Verify `artifact_hash` by recomputing SHA-256 of the artifact file (if file path available)
|
|
84
|
+
- For each attestation:
|
|
85
|
+
- Reconstruct evaluation_hash from: `judge_id|artifact_hash|score|rationale_summary`
|
|
86
|
+
- Compare reconstructed hash with stored `evaluation_hash`
|
|
87
|
+
- Verify `previous_attestation_hash` matches the preceding attestation's `evaluation_hash`
|
|
88
|
+
- Verify timestamp is after the preceding attestation's timestamp
|
|
89
|
+
- Report isolation status (all hashes valid = isolation confirmed)
|
|
90
|
+
|
|
91
|
+
### Step 4: Produce Verification Report
|
|
92
|
+
|
|
93
|
+
Display a formatted report for each directory:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
===============================================================
|
|
97
|
+
WITNESS CHAIN VERIFICATION REPORT
|
|
98
|
+
===============================================================
|
|
99
|
+
|
|
100
|
+
Directory: researches/bank_kc_automation/
|
|
101
|
+
|
|
102
|
+
--- Artifact Chain ---
|
|
103
|
+
[PASS] #0 phase-0 00_product_discovery.md
|
|
104
|
+
[PASS] #1 phase-1 01_case_brief.md
|
|
105
|
+
[PASS] #2 phase-2 02_research_findings.md
|
|
106
|
+
[FAIL] #3 phase-2.5 02.5_trend_brief.md
|
|
107
|
+
Expected: sha256:abc123...
|
|
108
|
+
Actual: sha256:def456...
|
|
109
|
+
>> Artifact may have been modified after checkpoint
|
|
110
|
+
[SKIP] #4 phase-3 03_solution_strategy.md (upstream chain broken)
|
|
111
|
+
|
|
112
|
+
Chain Status: BROKEN at record #3
|
|
113
|
+
Verified: 3/5 records
|
|
114
|
+
|
|
115
|
+
--- Judge Attestations ---
|
|
116
|
+
Evaluation: bto-eval-2026-03-01T12:00:00Z
|
|
117
|
+
[PASS] domain-expert score=8.2 hash verified
|
|
118
|
+
[PASS] critic score=7.5 hash verified, chain link valid
|
|
119
|
+
[PASS] completeness-auditor score=8.0 hash verified, chain link valid
|
|
120
|
+
|
|
121
|
+
Isolation Status: CONFIRMED
|
|
122
|
+
All 3 judges evaluated independently.
|
|
123
|
+
|
|
124
|
+
===============================================================
|
|
125
|
+
SUMMARY
|
|
126
|
+
===============================================================
|
|
127
|
+
|
|
128
|
+
Directories verified: 1
|
|
129
|
+
PASS: 0
|
|
130
|
+
FAIL: 1 (broken chain in bank_kc_automation)
|
|
131
|
+
NOT_FOUND: 0
|
|
132
|
+
|
|
133
|
+
Attestation sets verified: 1
|
|
134
|
+
Isolation confirmed: 1
|
|
135
|
+
Isolation violated: 0
|
|
136
|
+
|
|
137
|
+
===============================================================
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Step 5: Handle Edge Cases
|
|
141
|
+
|
|
142
|
+
| Scenario | Behavior |
|
|
143
|
+
|----------|----------|
|
|
144
|
+
| No `.witness-chain.json` in directory | Report NOT_FOUND, do not treat as error |
|
|
145
|
+
| Empty chain (no records) | Report WARNING: "Chain exists but contains no records" |
|
|
146
|
+
| Artifact file deleted | Report FAIL: "Artifact file not found" |
|
|
147
|
+
| Invalid JSON | Report FAIL: "Chain file corrupted — invalid JSON" |
|
|
148
|
+
| `sha256sum` not available | Try `shasum -a 256`, if also missing report ERROR |
|
|
149
|
+
| Directory does not exist | Report ERROR: "Directory not found" |
|
|
150
|
+
| Chain has gaps in sequence numbers | Report WARNING: "Non-contiguous sequence numbers" |
|
|
151
|
+
| Timestamps not monotonic | Report WARNING: "Timestamps not in chronological order" |
|
|
152
|
+
|
|
153
|
+
### Step 6: Summary and Recommendations
|
|
154
|
+
|
|
155
|
+
After all directories are verified, provide actionable recommendations:
|
|
156
|
+
|
|
157
|
+
- If a chain is broken: "Run the pipeline phase again to re-hash from the modified artifact forward"
|
|
158
|
+
- If attestation isolation is violated: "Review the BTO evaluation process — judges may have shared context"
|
|
159
|
+
- If no chains found: "Witness chains are created automatically during the /casarium pipeline when witness-chain rules are active"
|
|
160
|
+
|
|
161
|
+
## Quality Gates
|
|
162
|
+
|
|
163
|
+
- [ ] All target directories scanned
|
|
164
|
+
- [ ] Each record verified with hash recomputation (not just format check)
|
|
165
|
+
- [ ] Broken links reported with specific artifact name and hash values
|
|
166
|
+
- [ ] Judge attestation isolation checked (if attestations present)
|
|
167
|
+
- [ ] Clear summary with pass/fail counts
|
|
168
|
+
- [ ] Actionable recommendations provided for failures
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Witness Chain Integration Rules
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
These rules define when and how SHA-256 witness records and BTO judge attestations are created during the Keysarium pipeline. They integrate with the existing checkpoint protocol and BTO quality gates.
|
|
6
|
+
|
|
7
|
+
## Rule 1: Witness Record at Every Phase Checkpoint
|
|
8
|
+
|
|
9
|
+
**WHEN:** A phase checkpoint is reached and the phase artifact has been created.
|
|
10
|
+
**THEN:** Create a witness record before displaying the checkpoint banner.
|
|
11
|
+
|
|
12
|
+
### Procedure
|
|
13
|
+
|
|
14
|
+
1. After creating the phase artifact file, compute its witness record
|
|
15
|
+
2. If this is Phase 0 (first phase): create genesis record in `.witness-chain.json`
|
|
16
|
+
3. If this is Phase 1+: append record to existing `.witness-chain.json`
|
|
17
|
+
4. Include the witness hash in the checkpoint banner
|
|
18
|
+
|
|
19
|
+
### Integration with Checkpoint Format
|
|
20
|
+
|
|
21
|
+
The checkpoint banner gains a witness hash line:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
=============================================================
|
|
25
|
+
CHECKPOINT N: [Phase Name] Complete
|
|
26
|
+
<promise>[PROMISE_TAG]</promise>
|
|
27
|
+
Witness: sha256:<hash> (chain link #N)
|
|
28
|
+
|
|
29
|
+
[2-3 line summary of what was done]
|
|
30
|
+
Files created: [list]
|
|
31
|
+
|
|
32
|
+
* "ok" -- next phase
|
|
33
|
+
* "углуби [section]" -- elaborate
|
|
34
|
+
* "[specific feedback]" -- adjust
|
|
35
|
+
=============================================================
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Rule 2: Chain Structure Per Research
|
|
39
|
+
|
|
40
|
+
Each research directory (`researches/<slug>/`) has its own independent witness chain stored in `researches/<slug>/.witness-chain.json`.
|
|
41
|
+
|
|
42
|
+
### Chain initialization
|
|
43
|
+
|
|
44
|
+
- Created at Phase 0 (Discovery) with the genesis record
|
|
45
|
+
- Uses NULL_HASH (64 zeros) as the previous_hash for the first record
|
|
46
|
+
|
|
47
|
+
### Standard chain sequence
|
|
48
|
+
|
|
49
|
+
| Sequence | Phase | Artifact | Promise Tag |
|
|
50
|
+
|----------|-------|----------|-------------|
|
|
51
|
+
| 0 | phase-0 | 00_product_discovery.md | DISCOVERY_COMPLETE |
|
|
52
|
+
| 1 | phase-1 | 01_case_brief.md | CASE_EXPLORED |
|
|
53
|
+
| 2 | phase-2 | 02_research_findings.md | RESEARCH_PARANOID_PASSED |
|
|
54
|
+
| 3 | phase-2.5 | 02.5_trend_brief.md | CJM_VALIDATED |
|
|
55
|
+
| 4 | phase-3 | 03_solution_strategy.md | SOLUTION_DESIGNED |
|
|
56
|
+
| 5 | phase-4 | 04_architecture.md | ARCHITECTURE_DEFINED |
|
|
57
|
+
| 6 | phase-5 | 05_presentation_content.md | PRESENTATION_READY |
|
|
58
|
+
|
|
59
|
+
Additional artifacts (06_speaker_script.md, 07_qa_preparation.md, 08_executive_summary.md) may be added as additional chain records after sequence 6 if desired.
|
|
60
|
+
|
|
61
|
+
## Rule 3: Required Fields Per Witness Record
|
|
62
|
+
|
|
63
|
+
Every witness record MUST contain ALL of the following fields:
|
|
64
|
+
|
|
65
|
+
| Field | Type | Description |
|
|
66
|
+
|-------|------|-------------|
|
|
67
|
+
| `sequence` | integer | 0-based position, monotonically increasing |
|
|
68
|
+
| `phase` | string | Phase identifier (phase-0 through phase-5) |
|
|
69
|
+
| `artifact` | string | Filename relative to research directory |
|
|
70
|
+
| `hash` | string | `sha256:` prefix + 64 hex chars |
|
|
71
|
+
| `previous_hash` | string | `sha256:` prefix + 64 hex chars (or null hash for genesis) |
|
|
72
|
+
| `timestamp` | string | ISO-8601 UTC timestamp |
|
|
73
|
+
| `promise_tag` | string | The semantic promise tag for this phase |
|
|
74
|
+
|
|
75
|
+
No field may be empty, null, or omitted.
|
|
76
|
+
|
|
77
|
+
## Rule 4: BTO Judge Attestation Integration
|
|
78
|
+
|
|
79
|
+
**WHEN:** A BTO Layer 2 judge panel completes evaluation.
|
|
80
|
+
**THEN:** Create judge attestations in `.judge-attestations.json`.
|
|
81
|
+
|
|
82
|
+
### Procedure
|
|
83
|
+
|
|
84
|
+
1. Before the panel starts: compute the artifact_hash of the evaluated artifact
|
|
85
|
+
2. After each judge submits its score and rationale:
|
|
86
|
+
a. Compute evaluation_hash = SHA-256(judge_id|artifact_hash|score|rationale_summary)
|
|
87
|
+
b. Link to previous attestation hash (or NULL_HASH for first judge)
|
|
88
|
+
c. Record timestamp
|
|
89
|
+
3. After all judges complete: write `.judge-attestations.json` alongside the evaluation files
|
|
90
|
+
4. If meta-judge is invoked (disagreement > 3 points): append meta-judge attestation
|
|
91
|
+
|
|
92
|
+
### Attestation placement
|
|
93
|
+
|
|
94
|
+
The `.judge-attestations.json` file is stored in the same directory as the BTO evaluation output. If the BTO evaluation is for a skill, this could be:
|
|
95
|
+
- `researches/<slug>/` if BTO was run on a research artifact
|
|
96
|
+
- `.claude/skills/<name>/` if BTO was run on a skill
|
|
97
|
+
- The current working directory if path was specified explicitly
|
|
98
|
+
|
|
99
|
+
## Rule 5: Chain Repair After Legitimate Edits
|
|
100
|
+
|
|
101
|
+
If a user provides feedback at a checkpoint and the artifact is modified:
|
|
102
|
+
|
|
103
|
+
1. Re-hash the modified artifact using the same previous_hash
|
|
104
|
+
2. Update the record's hash in `.witness-chain.json`
|
|
105
|
+
3. If subsequent records exist: re-hash ALL downstream records (cascade)
|
|
106
|
+
4. Update `last_updated` timestamp
|
|
107
|
+
5. Log the repair in a `chain_repairs` array at the top level of the JSON:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"chain_repairs": [
|
|
112
|
+
{
|
|
113
|
+
"repaired_at": "2026-03-01T11:30:00Z",
|
|
114
|
+
"sequence": 2,
|
|
115
|
+
"artifact": "02_research_findings.md",
|
|
116
|
+
"reason": "User requested additional research depth",
|
|
117
|
+
"records_rehashed": 3
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This is expected behavior, not an error. The repair log provides audit context.
|
|
124
|
+
|
|
125
|
+
## Rule 6: Verification Recommendation
|
|
126
|
+
|
|
127
|
+
After completing a full pipeline (all phases through packaging):
|
|
128
|
+
- Recommend running `/verify-chain researches/<slug>/` as a final integrity check
|
|
129
|
+
- Include verification in the Phase 6 (Packaging) checklist
|
|
130
|
+
|
|
131
|
+
After completing a BTO evaluation:
|
|
132
|
+
- Recommend running `/verify-chain` on the evaluation directory to confirm judge isolation
|
|
133
|
+
|
|
134
|
+
## Rule 7: Graceful Degradation
|
|
135
|
+
|
|
136
|
+
If witness chain creation fails for any reason (sha256sum not available, file permission error, JSON write failure):
|
|
137
|
+
- Log a WARNING but do NOT block the pipeline
|
|
138
|
+
- The witness chain is an integrity enhancement, not a pipeline gate
|
|
139
|
+
- The pipeline must complete even without witness records
|
|
140
|
+
|
|
141
|
+
Exception: In banking domain (detected by Phase 0 domain detection), witness chain failure should be escalated to the user as a WARNING at the checkpoint, since audit trail is especially important for ФЗ-152 compliance.
|
|
142
|
+
|
|
143
|
+
## Rule 8: No Witness Chain for Feature ADR Pipeline
|
|
144
|
+
|
|
145
|
+
The `/feature-adr` pipeline does NOT require witness chains. Witness chains are designed for the Keysarium research pipeline (`/casarium`) and BTO evaluation pipeline (`/bto-test`).
|
|
146
|
+
|
|
147
|
+
If a user explicitly requests witness chain verification for a feature directory, `/verify-chain` should handle it gracefully (scan for `.witness-chain.json`, report NOT_FOUND if absent).
|
|
148
|
+
|
|
149
|
+
## Rule 9: Chain Files in .gitignore
|
|
150
|
+
|
|
151
|
+
The `.witness-chain.json` and `.judge-attestations.json` files SHOULD be committed to git alongside research artifacts. They are part of the audit trail and should be version-controlled.
|
|
152
|
+
|
|
153
|
+
Do NOT add these files to `.gitignore`.
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Judge Attestation Protocol
|
|
2
|
+
|
|
3
|
+
> Cryptographic proof of BTO judge isolation via SHA-256 hash chain attestations.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This protocol defines how BTO judges (Domain Expert, Critic, Completeness Auditor) create cryptographic attestations proving their evaluations were performed independently. Each judge records a hash of its evaluation before seeing other judges' scores. The attestations are chained to provide ordering proof.
|
|
8
|
+
|
|
9
|
+
## Constants
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
NULL_HASH = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
13
|
+
ATTESTATION_FILE = ".judge-attestations.json"
|
|
14
|
+
HASH_PREFIX = "sha256:"
|
|
15
|
+
STANDARD_PANEL_SIZE = 3
|
|
16
|
+
HIGH_STAKES_PANEL_SIZE = 5
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Section 1: Attestation Creation
|
|
20
|
+
|
|
21
|
+
### Hash Input Construction
|
|
22
|
+
|
|
23
|
+
Each judge's evaluation hash is computed from a deterministic concatenation of:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
evaluation_hash = SHA-256(judge_id + "|" + artifact_hash + "|" + score + "|" + rationale_summary)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The pipe character `|` serves as a delimiter to prevent ambiguity in concatenation.
|
|
30
|
+
|
|
31
|
+
**Fields:**
|
|
32
|
+
- `judge_id`: One of `"domain-expert"`, `"critic"`, `"completeness-auditor"` (or `"meta-judge"` for escalation)
|
|
33
|
+
- `artifact_hash`: SHA-256 hash of the artifact being evaluated (computed before evaluation starts)
|
|
34
|
+
- `score`: The numeric score as a string with one decimal place (e.g., `"8.2"`)
|
|
35
|
+
- `rationale_summary`: First 500 characters of the judge's rationale text, trimmed of leading/trailing whitespace
|
|
36
|
+
|
|
37
|
+
### Computing the Artifact Hash
|
|
38
|
+
|
|
39
|
+
Before any judge starts evaluation, compute the artifact hash:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
ARTIFACT_HASH=$(${SHA_CMD} "path/to/artifact.md" | awk '{print $1}')
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This hash is shared with all judges and recorded in each attestation. It proves all judges evaluated the same artifact.
|
|
46
|
+
|
|
47
|
+
### Computing the Evaluation Hash
|
|
48
|
+
|
|
49
|
+
After a judge completes its evaluation:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
JUDGE_ID="domain-expert"
|
|
53
|
+
ARTIFACT_HASH="<computed above>"
|
|
54
|
+
SCORE="8.2"
|
|
55
|
+
RATIONALE_SUMMARY="<first 500 chars of rationale>"
|
|
56
|
+
|
|
57
|
+
EVAL_HASH=$(printf '%s' "${JUDGE_ID}|${ARTIFACT_HASH}|${SCORE}|${RATIONALE_SUMMARY}" | ${SHA_CMD} | awk '{print $1}')
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Important:** The rationale_summary must be trimmed to exactly 500 characters (or fewer if the rationale is shorter) to ensure deterministic reproduction during verification.
|
|
61
|
+
|
|
62
|
+
## Section 2: Chain Linking
|
|
63
|
+
|
|
64
|
+
### Attestation Order
|
|
65
|
+
|
|
66
|
+
Judges are recorded in a fixed order within each evaluation round:
|
|
67
|
+
|
|
68
|
+
1. Domain Expert (first, previous = NULL_HASH)
|
|
69
|
+
2. Critic (second, previous = Domain Expert's evaluation_hash)
|
|
70
|
+
3. Completeness Auditor (third, previous = Critic's evaluation_hash)
|
|
71
|
+
|
|
72
|
+
For a 5-judge high-stakes panel:
|
|
73
|
+
4. Additional Expert (fourth, previous = Auditor's evaluation_hash)
|
|
74
|
+
5. Tiebreaker (fifth, previous = Additional Expert's evaluation_hash)
|
|
75
|
+
|
|
76
|
+
### Chain Link Protocol
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Judge 1 (Domain Expert)
|
|
80
|
+
PREV_ATTESTATION_HASH="${NULL_HASH}"
|
|
81
|
+
# ... compute evaluation_hash_1 ...
|
|
82
|
+
# Record attestation with previous = NULL_HASH
|
|
83
|
+
|
|
84
|
+
# Judge 2 (Critic)
|
|
85
|
+
PREV_ATTESTATION_HASH="${EVAL_HASH_1}"
|
|
86
|
+
# ... compute evaluation_hash_2 ...
|
|
87
|
+
# Record attestation with previous = evaluation_hash_1
|
|
88
|
+
|
|
89
|
+
# Judge 3 (Completeness Auditor)
|
|
90
|
+
PREV_ATTESTATION_HASH="${EVAL_HASH_2}"
|
|
91
|
+
# ... compute evaluation_hash_3 ...
|
|
92
|
+
# Record attestation with previous = evaluation_hash_2
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Ordering Note
|
|
96
|
+
|
|
97
|
+
The chain records the ORDER in which attestations were finalized, not the order in which judges started evaluating. Since judges evaluate in parallel but record sequentially, the chain proves:
|
|
98
|
+
- Each attestation was recorded after the previous one
|
|
99
|
+
- The evaluation_hash was computed from the judge's OWN data only
|
|
100
|
+
- The previous_attestation_hash links to the preceding record (not to the preceding judge's score)
|
|
101
|
+
|
|
102
|
+
## Section 3: Isolation Verification
|
|
103
|
+
|
|
104
|
+
### Verification Algorithm
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
1. Load .judge-attestations.json
|
|
108
|
+
2. For each evaluation round:
|
|
109
|
+
a. Verify artifact_hash matches across all attestations
|
|
110
|
+
b. For each attestation (index i):
|
|
111
|
+
i. Reconstruct evaluation_hash from (judge_id, artifact_hash, score, rationale_summary)
|
|
112
|
+
ii. Compare reconstructed hash with stored evaluation_hash
|
|
113
|
+
iii. If i == 0: verify previous_attestation_hash == NULL_HASH
|
|
114
|
+
iv. If i > 0: verify previous_attestation_hash == attestations[i-1].evaluation_hash
|
|
115
|
+
v. Verify timestamp > attestations[i-1].timestamp (if i > 0)
|
|
116
|
+
3. Produce verification report
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Isolation Proof Logic
|
|
120
|
+
|
|
121
|
+
The isolation proof works as follows:
|
|
122
|
+
|
|
123
|
+
1. **Hash Independence:** Each evaluation_hash is computed from `judge_id|artifact_hash|score|rationale`. If a judge had seen another judge's score, it would not affect its own hash computation -- but it would be detectable if the rationale text references specific scores from other judges.
|
|
124
|
+
|
|
125
|
+
2. **Chain Integrity:** The previous_attestation_hash links attestations in order but does NOT include other judges' scores in the hash input. This means the chain proves ORDER but the evaluation content is provably independent.
|
|
126
|
+
|
|
127
|
+
3. **Timestamp Monotonicity:** Timestamps must be strictly increasing. If Judge B's timestamp is BEFORE Judge A's, but Judge B's previous_attestation_hash points to Judge A, then something is wrong.
|
|
128
|
+
|
|
129
|
+
### Verification Script
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python3 << 'VERIFY_ATTESTATION'
|
|
133
|
+
import json, subprocess, sys
|
|
134
|
+
|
|
135
|
+
NULL_HASH = "0" * 64
|
|
136
|
+
|
|
137
|
+
def sha256(content):
|
|
138
|
+
result = subprocess.run(
|
|
139
|
+
["sha256sum"],
|
|
140
|
+
input=content.encode(),
|
|
141
|
+
capture_output=True
|
|
142
|
+
)
|
|
143
|
+
if result.returncode != 0:
|
|
144
|
+
result = subprocess.run(
|
|
145
|
+
["shasum", "-a", "256"],
|
|
146
|
+
input=content.encode(),
|
|
147
|
+
capture_output=True
|
|
148
|
+
)
|
|
149
|
+
return result.stdout.decode().split()[0]
|
|
150
|
+
|
|
151
|
+
DIR = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
152
|
+
|
|
153
|
+
with open(f"{DIR}/.judge-attestations.json") as f:
|
|
154
|
+
data = json.load(f)
|
|
155
|
+
|
|
156
|
+
for eval_round in data.get("evaluations", [data]):
|
|
157
|
+
attestations = eval_round.get("attestations", [])
|
|
158
|
+
artifact_hash = eval_round.get("artifact_hash", "")
|
|
159
|
+
violations = []
|
|
160
|
+
|
|
161
|
+
for i, att in enumerate(attestations):
|
|
162
|
+
# Reconstruct evaluation hash
|
|
163
|
+
hash_input = f"{att['judge_id']}|{artifact_hash}|{att['score']}|{att.get('rationale_summary', '')}"
|
|
164
|
+
expected = sha256(hash_input)
|
|
165
|
+
actual = att["evaluation_hash"].replace("sha256:", "")
|
|
166
|
+
|
|
167
|
+
if expected != actual:
|
|
168
|
+
violations.append(f"Judge {att['judge_id']}: evaluation_hash mismatch")
|
|
169
|
+
|
|
170
|
+
# Verify chain link
|
|
171
|
+
if i == 0:
|
|
172
|
+
expected_prev = NULL_HASH
|
|
173
|
+
else:
|
|
174
|
+
expected_prev = attestations[i-1]["evaluation_hash"].replace("sha256:", "")
|
|
175
|
+
|
|
176
|
+
actual_prev = att["previous_attestation_hash"].replace("sha256:", "")
|
|
177
|
+
if expected_prev != actual_prev:
|
|
178
|
+
violations.append(f"Judge {att['judge_id']}: chain link broken")
|
|
179
|
+
|
|
180
|
+
if not violations:
|
|
181
|
+
print(f"PASS: All {len(attestations)} judge attestations verified. Isolation confirmed.")
|
|
182
|
+
else:
|
|
183
|
+
print(f"FAIL: {len(violations)} violations detected:")
|
|
184
|
+
for v in violations:
|
|
185
|
+
print(f" - {v}")
|
|
186
|
+
|
|
187
|
+
VERIFY_ATTESTATION
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Section 4: File Format
|
|
191
|
+
|
|
192
|
+
### .judge-attestations.json Schema
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"evaluations": [
|
|
197
|
+
{
|
|
198
|
+
"evaluation_id": "string — unique ID for this evaluation round",
|
|
199
|
+
"artifact_path": "string — path to the evaluated artifact",
|
|
200
|
+
"artifact_hash": "string — sha256:<hex> of the artifact content",
|
|
201
|
+
"panel_size": "integer — 3 (standard) or 5 (high-stakes)",
|
|
202
|
+
"started_at": "ISO-8601 — when evaluation round began",
|
|
203
|
+
"completed_at": "ISO-8601 — when last attestation recorded",
|
|
204
|
+
"attestations": [
|
|
205
|
+
{
|
|
206
|
+
"judge_id": "string — domain-expert|critic|completeness-auditor",
|
|
207
|
+
"score": "float — 0.0-10.0",
|
|
208
|
+
"rationale_summary": "string — first 500 chars of rationale",
|
|
209
|
+
"evaluation_hash": "string — sha256:<hex>",
|
|
210
|
+
"timestamp": "ISO-8601 — when attestation was recorded",
|
|
211
|
+
"previous_attestation_hash": "string — sha256:<hex> or null hash"
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"final_score": "float — weighted average",
|
|
215
|
+
"weights": {
|
|
216
|
+
"domain-expert": 0.4,
|
|
217
|
+
"critic": 0.3,
|
|
218
|
+
"completeness-auditor": 0.3
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Example Attestation File
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"evaluations": [
|
|
230
|
+
{
|
|
231
|
+
"evaluation_id": "bto-eval-2026-03-01T12:00:00Z",
|
|
232
|
+
"artifact_path": ".claude/skills/explore/SKILL.md",
|
|
233
|
+
"artifact_hash": "sha256:abc123def456789012345678901234567890123456789012345678901234fedc",
|
|
234
|
+
"panel_size": 3,
|
|
235
|
+
"started_at": "2026-03-01T12:00:00Z",
|
|
236
|
+
"completed_at": "2026-03-01T12:01:45Z",
|
|
237
|
+
"attestations": [
|
|
238
|
+
{
|
|
239
|
+
"judge_id": "domain-expert",
|
|
240
|
+
"score": 8.2,
|
|
241
|
+
"rationale_summary": "Strong SKILL.md with clear protocol steps. References directory provides good examples...",
|
|
242
|
+
"evaluation_hash": "sha256:111222333444555666777888999000aaabbbcccdddeeefff000111222333444555",
|
|
243
|
+
"timestamp": "2026-03-01T12:00:30Z",
|
|
244
|
+
"previous_attestation_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"judge_id": "critic",
|
|
248
|
+
"score": 7.5,
|
|
249
|
+
"rationale_summary": "Missing edge case handling for multi-language inputs. Error recovery protocol unclear...",
|
|
250
|
+
"evaluation_hash": "sha256:222333444555666777888999000aaabbbcccdddeeefff000111222333444555666",
|
|
251
|
+
"timestamp": "2026-03-01T12:01:00Z",
|
|
252
|
+
"previous_attestation_hash": "sha256:111222333444555666777888999000aaabbbcccdddeeefff000111222333444555"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"judge_id": "completeness-auditor",
|
|
256
|
+
"score": 8.0,
|
|
257
|
+
"rationale_summary": "All required sections present. References directory has 3 examples. Missing: performance benchmarks...",
|
|
258
|
+
"evaluation_hash": "sha256:333444555666777888999000aaabbbcccdddeeefff000111222333444555666777",
|
|
259
|
+
"timestamp": "2026-03-01T12:01:30Z",
|
|
260
|
+
"previous_attestation_hash": "sha256:222333444555666777888999000aaabbbcccdddeeefff000111222333444555666"
|
|
261
|
+
}
|
|
262
|
+
],
|
|
263
|
+
"final_score": 7.94,
|
|
264
|
+
"weights": {
|
|
265
|
+
"domain-expert": 0.4,
|
|
266
|
+
"critic": 0.3,
|
|
267
|
+
"completeness-auditor": 0.3
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Section 5: Integration with BTO Pipeline
|
|
275
|
+
|
|
276
|
+
### When to Create Attestations
|
|
277
|
+
|
|
278
|
+
Attestations are created during **BTO Layer 2 evaluation** (the judge panel stage):
|
|
279
|
+
|
|
280
|
+
1. **Before panel starts:** Compute `artifact_hash` of the artifact being evaluated
|
|
281
|
+
2. **After each judge completes:** Create attestation record with evaluation_hash
|
|
282
|
+
3. **After all judges complete:** Write `.judge-attestations.json`
|
|
283
|
+
4. **If meta-judge is invoked:** Add a 4th attestation with `judge_id: "meta-judge"`
|
|
284
|
+
|
|
285
|
+
### Integration Points
|
|
286
|
+
|
|
287
|
+
| BTO Stage | Attestation Action |
|
|
288
|
+
|-----------|-------------------|
|
|
289
|
+
| Layer 0 (structural) | No attestation needed (deterministic checks) |
|
|
290
|
+
| Layer 1 (semantic) | No attestation needed (single haiku agent) |
|
|
291
|
+
| Layer 2 (judge panel) | Create attestation set with 3 attestations |
|
|
292
|
+
| Meta-judge (escalation) | Append meta-judge attestation to the set |
|
|
293
|
+
| Optimization rounds | Create new attestation set per round's final evaluation |
|
|
294
|
+
|
|
295
|
+
### Disagreement Detection Enhancement
|
|
296
|
+
|
|
297
|
+
With attestations, disagreement detection gains cryptographic backing:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
If max_score - min_score > 3.0:
|
|
301
|
+
1. Verify all attestations are valid (no tampering)
|
|
302
|
+
2. Verify isolation (no judge influenced another)
|
|
303
|
+
3. If isolation verified: escalate to meta-judge (genuine disagreement)
|
|
304
|
+
4. If isolation violated: flag conformity collapse warning
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Section 6: Meta-Judge Attestation
|
|
308
|
+
|
|
309
|
+
When the meta-judge is invoked (disagreement > 3 points), it creates its own attestation:
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"judge_id": "meta-judge",
|
|
314
|
+
"score": 7.8,
|
|
315
|
+
"rationale_summary": "Disagreement between domain-expert (8.5) and critic (5.2) on dimension 'completeness'...",
|
|
316
|
+
"evaluation_hash": "sha256:...",
|
|
317
|
+
"timestamp": "2026-03-01T12:02:00Z",
|
|
318
|
+
"previous_attestation_hash": "sha256:<last judge's eval hash>"
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The meta-judge's rationale MAY reference other judges' scores (it has access to them for arbitration). This is expected and does not violate isolation -- the meta-judge is explicitly a synthesizer, not an independent evaluator.
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# Witness Chain Protocol
|
|
2
|
+
|
|
3
|
+
> SHA-256 hash-chain for tamper-evident artifact integrity verification.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This protocol defines how to compute SHA-256 hashes of pipeline artifacts and link them into a chain. Each artifact's hash includes the previous artifact's hash, making the chain tamper-evident: modifying any artifact breaks all downstream hashes.
|
|
8
|
+
|
|
9
|
+
## Constants
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
NULL_HASH = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
13
|
+
CHAIN_FILE = ".witness-chain.json"
|
|
14
|
+
HASH_PREFIX = "sha256:"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Section 1: Hash Computation
|
|
18
|
+
|
|
19
|
+
### Platform Detection
|
|
20
|
+
|
|
21
|
+
Detect the available SHA-256 command:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Try sha256sum first (Linux), then shasum (macOS)
|
|
25
|
+
if command -v sha256sum &>/dev/null; then
|
|
26
|
+
SHA_CMD="sha256sum"
|
|
27
|
+
elif command -v shasum &>/dev/null; then
|
|
28
|
+
SHA_CMD="shasum -a 256"
|
|
29
|
+
else
|
|
30
|
+
echo "ERROR: No SHA-256 command found. Install coreutils."
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Computing a File Hash
|
|
36
|
+
|
|
37
|
+
Hash a single file's content:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
HASH=$(${SHA_CMD} "path/to/file.md" | awk '{print $1}')
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Computing a Chained Hash
|
|
44
|
+
|
|
45
|
+
Hash file content concatenated with the previous hash to create a chain link:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Read file content and previous hash
|
|
49
|
+
FILE_CONTENT=$(cat "path/to/file.md")
|
|
50
|
+
PREV_HASH="<previous hash value>"
|
|
51
|
+
|
|
52
|
+
# Compute chained hash
|
|
53
|
+
CHAINED_HASH=$(printf '%s%s' "${FILE_CONTENT}" "${PREV_HASH}" | ${SHA_CMD} | awk '{print $1}')
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Important:** Use `printf '%s%s'` (not `echo`) to avoid trailing newline issues. The content is the raw file bytes followed immediately by the 64-character hex hash string.
|
|
57
|
+
|
|
58
|
+
### Content Normalization
|
|
59
|
+
|
|
60
|
+
Before hashing, the content should be the raw file content as stored on disk. Do NOT:
|
|
61
|
+
- Trim whitespace
|
|
62
|
+
- Normalize line endings
|
|
63
|
+
- Strip BOM markers
|
|
64
|
+
|
|
65
|
+
The hash must match the exact bytes in the file so that verification produces the same result.
|
|
66
|
+
|
|
67
|
+
## Section 2: Chain Operations
|
|
68
|
+
|
|
69
|
+
### Operation: Create Genesis Record
|
|
70
|
+
|
|
71
|
+
Called at Phase 0 (Discovery) to initialize the chain for a new research.
|
|
72
|
+
|
|
73
|
+
**Preconditions:**
|
|
74
|
+
- Research directory `researches/<slug>/` exists
|
|
75
|
+
- Phase 0 artifact `00_product_discovery.md` has been created
|
|
76
|
+
- No `.witness-chain.json` exists yet
|
|
77
|
+
|
|
78
|
+
**Procedure:**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
SLUG="<research-slug>"
|
|
82
|
+
DIR="researches/${SLUG}"
|
|
83
|
+
ARTIFACT="00_product_discovery.md"
|
|
84
|
+
PREV_HASH="${NULL_HASH}"
|
|
85
|
+
|
|
86
|
+
# Compute genesis hash
|
|
87
|
+
FILE_CONTENT=$(cat "${DIR}/${ARTIFACT}")
|
|
88
|
+
HASH=$(printf '%s%s' "${FILE_CONTENT}" "${PREV_HASH}" | ${SHA_CMD} | awk '{print $1}')
|
|
89
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
90
|
+
|
|
91
|
+
# Create .witness-chain.json
|
|
92
|
+
cat > "${DIR}/.witness-chain.json" << ENDOFCHAIN
|
|
93
|
+
{
|
|
94
|
+
"research_slug": "${SLUG}",
|
|
95
|
+
"created_at": "${TIMESTAMP}",
|
|
96
|
+
"last_updated": "${TIMESTAMP}",
|
|
97
|
+
"chain": [
|
|
98
|
+
{
|
|
99
|
+
"sequence": 0,
|
|
100
|
+
"phase": "phase-0",
|
|
101
|
+
"artifact": "${ARTIFACT}",
|
|
102
|
+
"hash": "${HASH_PREFIX}${HASH}",
|
|
103
|
+
"previous_hash": "${HASH_PREFIX}${PREV_HASH}",
|
|
104
|
+
"timestamp": "${TIMESTAMP}",
|
|
105
|
+
"promise_tag": "DISCOVERY_COMPLETE"
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
ENDOFCHAIN
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Operation: Append Record
|
|
113
|
+
|
|
114
|
+
Called at each subsequent phase checkpoint to add a new record to the chain.
|
|
115
|
+
|
|
116
|
+
**Preconditions:**
|
|
117
|
+
- `.witness-chain.json` exists in the research directory
|
|
118
|
+
- The new artifact file has been created
|
|
119
|
+
- The previous phase's record exists in the chain
|
|
120
|
+
|
|
121
|
+
**Procedure:**
|
|
122
|
+
|
|
123
|
+
1. Read `.witness-chain.json` and extract the last record's hash
|
|
124
|
+
2. Compute the new chained hash: `SHA-256(new_content + last_hash)`
|
|
125
|
+
3. Append the new record to the chain array
|
|
126
|
+
4. Update `last_updated` timestamp
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
SLUG="<research-slug>"
|
|
130
|
+
DIR="researches/${SLUG}"
|
|
131
|
+
ARTIFACT="<artifact-filename>"
|
|
132
|
+
PHASE="<phase-id>"
|
|
133
|
+
PROMISE="<promise-tag>"
|
|
134
|
+
|
|
135
|
+
# Get the latest hash from the chain
|
|
136
|
+
PREV_HASH=$(cat "${DIR}/.witness-chain.json" | python3 -c "
|
|
137
|
+
import json, sys
|
|
138
|
+
chain = json.load(sys.stdin)
|
|
139
|
+
last = chain['chain'][-1]
|
|
140
|
+
print(last['hash'].replace('sha256:', ''))
|
|
141
|
+
")
|
|
142
|
+
|
|
143
|
+
# Compute new hash
|
|
144
|
+
FILE_CONTENT=$(cat "${DIR}/${ARTIFACT}")
|
|
145
|
+
HASH=$(printf '%s%s' "${FILE_CONTENT}" "${PREV_HASH}" | ${SHA_CMD} | awk '{print $1}')
|
|
146
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
147
|
+
SEQUENCE=$(cat "${DIR}/.witness-chain.json" | python3 -c "
|
|
148
|
+
import json, sys
|
|
149
|
+
chain = json.load(sys.stdin)
|
|
150
|
+
print(len(chain['chain']))
|
|
151
|
+
")
|
|
152
|
+
|
|
153
|
+
# Append to chain (using python3 for safe JSON manipulation)
|
|
154
|
+
python3 -c "
|
|
155
|
+
import json, sys
|
|
156
|
+
with open('${DIR}/.witness-chain.json', 'r') as f:
|
|
157
|
+
chain = json.load(f)
|
|
158
|
+
chain['chain'].append({
|
|
159
|
+
'sequence': ${SEQUENCE},
|
|
160
|
+
'phase': '${PHASE}',
|
|
161
|
+
'artifact': '${ARTIFACT}',
|
|
162
|
+
'hash': 'sha256:${HASH}',
|
|
163
|
+
'previous_hash': 'sha256:${PREV_HASH}',
|
|
164
|
+
'timestamp': '${TIMESTAMP}',
|
|
165
|
+
'promise_tag': '${PROMISE}'
|
|
166
|
+
})
|
|
167
|
+
chain['last_updated'] = '${TIMESTAMP}'
|
|
168
|
+
with open('${DIR}/.witness-chain.json', 'w') as f:
|
|
169
|
+
json.dump(chain, f, indent=2)
|
|
170
|
+
"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Operation: Get Latest Hash
|
|
174
|
+
|
|
175
|
+
Retrieve the most recent hash from the chain (needed as input for the next append).
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
LATEST_HASH=$(python3 -c "
|
|
179
|
+
import json
|
|
180
|
+
with open('${DIR}/.witness-chain.json') as f:
|
|
181
|
+
chain = json.load(f)
|
|
182
|
+
print(chain['chain'][-1]['hash'].replace('sha256:', ''))
|
|
183
|
+
")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Section 3: Verification
|
|
187
|
+
|
|
188
|
+
### Operation: Verify Chain
|
|
189
|
+
|
|
190
|
+
Walk the entire chain and verify each hash matches the current file content.
|
|
191
|
+
|
|
192
|
+
**Algorithm:**
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
1. Load .witness-chain.json
|
|
196
|
+
2. For each record in chain (index i):
|
|
197
|
+
a. Read the artifact file
|
|
198
|
+
b. Get previous_hash:
|
|
199
|
+
- If i == 0: previous_hash = NULL_HASH
|
|
200
|
+
- Else: previous_hash = chain[i-1].hash (without prefix)
|
|
201
|
+
c. Compute expected = SHA-256(file_content + previous_hash)
|
|
202
|
+
d. Compare expected with record.hash (without prefix)
|
|
203
|
+
e. If mismatch: record broken link
|
|
204
|
+
3. Produce verification report
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Bash implementation:**
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
DIR="researches/<slug>"
|
|
211
|
+
|
|
212
|
+
python3 << 'VERIFY_SCRIPT'
|
|
213
|
+
import json, subprocess, sys
|
|
214
|
+
|
|
215
|
+
NULL_HASH = "0" * 64
|
|
216
|
+
DIR = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
217
|
+
|
|
218
|
+
def sha256(content):
|
|
219
|
+
"""Compute SHA-256 of a string."""
|
|
220
|
+
result = subprocess.run(
|
|
221
|
+
["sha256sum"],
|
|
222
|
+
input=content.encode(),
|
|
223
|
+
capture_output=True
|
|
224
|
+
)
|
|
225
|
+
if result.returncode != 0:
|
|
226
|
+
# Fallback to shasum
|
|
227
|
+
result = subprocess.run(
|
|
228
|
+
["shasum", "-a", "256"],
|
|
229
|
+
input=content.encode(),
|
|
230
|
+
capture_output=True
|
|
231
|
+
)
|
|
232
|
+
return result.stdout.decode().split()[0]
|
|
233
|
+
|
|
234
|
+
# Load chain
|
|
235
|
+
with open(f"{DIR}/.witness-chain.json") as f:
|
|
236
|
+
chain_data = json.load(f)
|
|
237
|
+
|
|
238
|
+
records = chain_data["chain"]
|
|
239
|
+
broken = []
|
|
240
|
+
verified = 0
|
|
241
|
+
|
|
242
|
+
for i, record in enumerate(records):
|
|
243
|
+
artifact_path = f"{DIR}/{record['artifact']}"
|
|
244
|
+
try:
|
|
245
|
+
with open(artifact_path) as f:
|
|
246
|
+
content = f.read()
|
|
247
|
+
except FileNotFoundError:
|
|
248
|
+
broken.append({
|
|
249
|
+
"sequence": i,
|
|
250
|
+
"artifact": record["artifact"],
|
|
251
|
+
"reason": "File not found"
|
|
252
|
+
})
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
if i == 0:
|
|
256
|
+
prev_hash = NULL_HASH
|
|
257
|
+
else:
|
|
258
|
+
prev_hash = records[i-1]["hash"].replace("sha256:", "")
|
|
259
|
+
|
|
260
|
+
expected = sha256(content + prev_hash)
|
|
261
|
+
actual = record["hash"].replace("sha256:", "")
|
|
262
|
+
|
|
263
|
+
if expected == actual:
|
|
264
|
+
verified += 1
|
|
265
|
+
else:
|
|
266
|
+
broken.append({
|
|
267
|
+
"sequence": i,
|
|
268
|
+
"artifact": record["artifact"],
|
|
269
|
+
"expected_hash": f"sha256:{expected}",
|
|
270
|
+
"actual_hash": record["hash"],
|
|
271
|
+
"reason": "Hash mismatch - artifact may have been modified"
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
# Report
|
|
275
|
+
total = len(records)
|
|
276
|
+
if not broken:
|
|
277
|
+
print(f"PASS: All {total} records verified successfully")
|
|
278
|
+
else:
|
|
279
|
+
print(f"FAIL: {len(broken)} broken links out of {total} records")
|
|
280
|
+
for b in broken:
|
|
281
|
+
print(f" - Record {b['sequence']} ({b['artifact']}): {b['reason']}")
|
|
282
|
+
|
|
283
|
+
VERIFY_SCRIPT
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Section 4: File Format
|
|
287
|
+
|
|
288
|
+
### .witness-chain.json Schema
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"research_slug": "string — research directory name",
|
|
293
|
+
"created_at": "ISO-8601 — when chain was initialized",
|
|
294
|
+
"last_updated": "ISO-8601 — when last record was added",
|
|
295
|
+
"chain": [
|
|
296
|
+
{
|
|
297
|
+
"sequence": "integer — 0-based position in chain",
|
|
298
|
+
"phase": "string — phase-0, phase-1, ..., phase-5",
|
|
299
|
+
"artifact": "string — filename relative to research dir",
|
|
300
|
+
"hash": "string — sha256:<64 hex chars>",
|
|
301
|
+
"previous_hash": "string — sha256:<64 hex chars> or sha256:<null hash>",
|
|
302
|
+
"timestamp": "ISO-8601 — when record was created",
|
|
303
|
+
"promise_tag": "string — semantic completion promise"
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Standard Phase-to-Artifact Mapping
|
|
310
|
+
|
|
311
|
+
| Phase | Artifact | Promise Tag |
|
|
312
|
+
|-------|----------|-------------|
|
|
313
|
+
| phase-0 | 00_product_discovery.md | DISCOVERY_COMPLETE |
|
|
314
|
+
| phase-1 | 01_case_brief.md | CASE_EXPLORED |
|
|
315
|
+
| phase-2 | 02_research_findings.md | RESEARCH_PARANOID_PASSED |
|
|
316
|
+
| phase-2.5 | 02.5_trend_brief.md | CJM_VALIDATED |
|
|
317
|
+
| phase-3 | 03_solution_strategy.md | SOLUTION_DESIGNED |
|
|
318
|
+
| phase-4 | 04_architecture.md | ARCHITECTURE_DEFINED |
|
|
319
|
+
| phase-5 | 05_presentation_content.md | PRESENTATION_READY |
|
|
320
|
+
|
|
321
|
+
### Example Complete Chain
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"research_slug": "bank_kc_automation",
|
|
326
|
+
"created_at": "2026-03-01T10:00:00Z",
|
|
327
|
+
"last_updated": "2026-03-01T14:30:00Z",
|
|
328
|
+
"chain": [
|
|
329
|
+
{
|
|
330
|
+
"sequence": 0,
|
|
331
|
+
"phase": "phase-0",
|
|
332
|
+
"artifact": "00_product_discovery.md",
|
|
333
|
+
"hash": "sha256:a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
|
|
334
|
+
"previous_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
335
|
+
"timestamp": "2026-03-01T10:00:00Z",
|
|
336
|
+
"promise_tag": "DISCOVERY_COMPLETE"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"sequence": 1,
|
|
340
|
+
"phase": "phase-1",
|
|
341
|
+
"artifact": "01_case_brief.md",
|
|
342
|
+
"hash": "sha256:b2c3d4e5f67890123456789012345678901234567890123456789012345bef01",
|
|
343
|
+
"previous_hash": "sha256:a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
|
|
344
|
+
"timestamp": "2026-03-01T10:30:00Z",
|
|
345
|
+
"promise_tag": "CASE_EXPLORED"
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Section 5: Chain Repair
|
|
352
|
+
|
|
353
|
+
If an artifact is legitimately modified (e.g., user requested changes during checkpoint review), the chain must be repaired:
|
|
354
|
+
|
|
355
|
+
1. Re-hash the modified artifact using the previous record's hash
|
|
356
|
+
2. Update the modified record's hash in `.witness-chain.json`
|
|
357
|
+
3. Re-hash ALL subsequent records (cascade update)
|
|
358
|
+
4. Update `last_updated` timestamp
|
|
359
|
+
|
|
360
|
+
This is expected and normal. The witness chain protects against *undetected* modifications, not against all modifications.
|
|
361
|
+
|
|
362
|
+
## Section 6: Platform Compatibility
|
|
363
|
+
|
|
364
|
+
| Platform | Command | Notes |
|
|
365
|
+
|----------|---------|-------|
|
|
366
|
+
| Linux (Ubuntu, Debian, etc.) | `sha256sum` | Available by default in coreutils |
|
|
367
|
+
| macOS | `shasum -a 256` | Available by default |
|
|
368
|
+
| Windows (WSL) | `sha256sum` | Available in WSL |
|
|
369
|
+
| Windows (Git Bash) | `sha256sum` | Available in Git for Windows |
|
|
370
|
+
| Alpine Linux | `sha256sum` | Available in coreutils |
|
|
371
|
+
|
|
372
|
+
The protocol should attempt `sha256sum` first, then fall back to `shasum -a 256`.
|