docs(P03): research findings — L2/contract/state/audit/core-script schemas
---ci---
phase: 3
milestone: v1.0
status: research
research:
l2_schema: l1s list of {name, inputs: map}; 4 L2s each referencing 5 L1s
contract_schema: stack + inputs + optional public-ingress: bool
state_json_shape: l2, l1s array (name+applied+exit_code), contract
audit_json: JSON array; canonical-JSON SHA-256 hash chain; GENESIS prev_hash
core_script_io: 5 scripts with explicit input/output/exit contracts
personas: no change; backend-engineer owns core scripts + L2 manifests; infra-stub-engineer owns L1 manifests only
---/ci---
ARCHITECTURE.md gains the L2 manifest schema, the L2 list with per-L2
L1 references (5 each, within max-depth-5), the contract.yaml schema,
the state.json shape, the audit.json event + canonical-JSON hash chain,
and a core-script I/O contract table. PERSONAS.md is unchanged for
Phase 03 (backend-engineer and infra-stub-engineer were both already
active in the project-level roster).
This commit is contained in:
+109
-1
@@ -143,4 +143,112 @@ Each L1 module lives at `modules/l1/<name>/` with exactly two files:
|
||||
| `l1-cloudwatch` | Observability primitive |
|
||||
|
||||
L1 modules are single-purpose, substrate-agnostic, max-depth-1 (per
|
||||
PROJECT.md Constraints). They do not compose with other L1s.
|
||||
PROJECT.md Constraints). They do not compose with other L1s.
|
||||
|
||||
## L2 module schema + core scripts (Phase 03 research)
|
||||
|
||||
### L2 manifest.yaml schema (D-020)
|
||||
|
||||
```yaml
|
||||
name: l2-commodity-price-feed # matches the folder name
|
||||
kind: l2 # literal "l2"
|
||||
description: <one-line>
|
||||
l1s: # ordered list of L1 references
|
||||
- name: l1-eks-fargate # MUST match an existing L1 folder name
|
||||
inputs:
|
||||
cluster_name: price-feed-cluster
|
||||
region: us-east-1
|
||||
cpu_arch: arm64
|
||||
- name: l1-lambda
|
||||
inputs:
|
||||
function_name: price-ingest
|
||||
runtime: python3.11
|
||||
handler: index.handler
|
||||
# ... up to 5 L1 references per L2 (max-depth-5 per REQ-05; L2->L1 is depth 1)
|
||||
```
|
||||
|
||||
L2s reference L1s **by name only** (no path); `mock_executor.sh` resolves
|
||||
the name to `modules/l1/<name>/`.
|
||||
|
||||
### L2 list (fixed per REQ-04)
|
||||
|
||||
| Folder | Description | L1s (per S&P Global Energy / Platts use cases) |
|
||||
|--------|-------------|------------------------------------------------|
|
||||
| `l2-invoice-service` | Billing + invoicing microservice | `l1-eks-fargate`, `l1-iam-role`, `l1-lambda`, `l1-sqs`, `l1-s3` |
|
||||
| `l2-commodity-price-feed` | Real-time price ingestion | `l1-eks-fargate`, `l1-lambda`, `l1-api-gateway`, `l1-eventbridge`, `l1-s3` |
|
||||
| `l2-energy-analytics-api` | Historical query API | `l1-eks-fargate`, `l1-api-gateway`, `l1-lambda`, `l1-s3`, `l1-cloudwatch` |
|
||||
| `l2-regulatory-reporting` | Compliance + reporting | `l1-eks-fargate`, `l1-iam-role`, `l1-lambda`, `l1-sqs`, `l1-s3` |
|
||||
|
||||
Each L2 references exactly 5 L1s (within the max-depth-5 constraint; L2→L1
|
||||
is depth 1, so depth-5 is generous but the spec caps composition depth at
|
||||
5 — the count is 5 to demonstrate a realistic composed stack).
|
||||
|
||||
### contract.yaml schema (D-021)
|
||||
|
||||
```yaml
|
||||
stack: l2-commodity-price-feed # MUST match an existing L2 folder name
|
||||
inputs: # top-level params for the L2 (optional)
|
||||
environment: dev
|
||||
owner: platform-team
|
||||
public-ingress: false # bool; true triggers POLICY_VIOLATION:PUBLIC_INGRESS
|
||||
```
|
||||
|
||||
The `public-ingress` key is the only policy-enforced field in Phase 03.
|
||||
Phase 04's pipeline reads `contract.yaml`, runs `policy_checker.py`, then
|
||||
`mock_executor.sh` to apply the L2.
|
||||
|
||||
### state.json shape (D-022)
|
||||
|
||||
`mock_executor.sh` writes `state.json` to its working directory:
|
||||
|
||||
```json
|
||||
{
|
||||
"l2": "l2-commodity-price-feed",
|
||||
"l1s": [
|
||||
{"name": "l1-eks-fargate", "applied": true, "exit_code": 0},
|
||||
{"name": "l1-lambda", "applied": true, "exit_code": 0},
|
||||
...
|
||||
],
|
||||
"contract": {
|
||||
"stack": "l2-commodity-price-feed",
|
||||
"inputs": {...},
|
||||
"public-ingress": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### audit.json event + hash chain (D-023)
|
||||
|
||||
`audit.json` is a JSON array of event objects. `evidence_writer.py`
|
||||
appends one event per call. Hash chain:
|
||||
|
||||
1. Construct the event dict with `hash` set to empty string.
|
||||
2. Serialize via `json.dumps(event, sort_keys=True, separators=(",", ":"))` — canonical JSON (deterministic key order, no whitespace).
|
||||
3. Compute `hash = sha256(canonical_json.encode("utf-8")).hexdigest()`.
|
||||
4. Set `event["hash"] = hash`.
|
||||
5. Append to `audit.json`.
|
||||
|
||||
Genesis event (when `audit.json` is empty or missing):
|
||||
|
||||
```json
|
||||
{
|
||||
"seq": 0,
|
||||
"ts": "2026-07-21T13:00:00Z",
|
||||
"stage": "genesis",
|
||||
"event": "audit log initialized",
|
||||
"prev_hash": "GENESIS",
|
||||
"hash": "<sha256 of the canonical json of this event with hash empty>"
|
||||
}
|
||||
```
|
||||
|
||||
Subsequent events: `seq = prev.seq + 1`, `prev_hash = prev.hash`.
|
||||
|
||||
### Core script I/O contracts
|
||||
|
||||
| Script | Input | Output | Exit |
|
||||
|--------|-------|--------|------|
|
||||
| `mock_executor.sh` | `<contract.yaml path>` (argv[1]); reads L2 manifest from `modules/l2/<contract.stack>/manifest.yaml` | writes `state.json` to cwd; prints per-L1 progress | 0 on all-L1s-pass; non-zero on any L1 failure |
|
||||
| `policy_checker.py` | `<contract.yaml path>` (argv[1]) | stdout: `POLICY_PASS` or `POLICY_VIOLATION:PUBLIC_INGRESS` | 0 on pass; 1 on violation |
|
||||
| `confidence_signal.py` | `<contract.yaml path>` (argv[1]); calls policy_checker | stdout: `{"score": 0.90|0.40, "reason": "..."}` | 0 always (per D-024; pipeline decides gate) |
|
||||
| `evidence_writer.py` | argv: `--stage <dev|qa|prod|finalize|genesis>` `--event "<text>"` `--audit <path to audit.json>` (default `./audit.json`) | appends event to audit.json; prints the new event's hash + seq | 0 on success; 1 on I/O error |
|
||||
| `l3b_agent_stub.py` | argv[1] = issue body text (or stdin if no argv); optional `-o <path>` (default stdout) | writes a `contract.yaml` (D-021 schema) with `stack` set by the D-008 keyword map | 0 on success; 1 on empty input |
|
||||
Reference in New Issue
Block a user