Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions DOCS/Workplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Companion to [workplan.md](../workplan.md) M8 task stubs and [RFC §9](../RFC/Hy
| D1 | `TypedValue` = union `string/int/double/bool`; inferred at parse time in `CascadeSheetReader.parseProperty` (no new syntax). |
| D2 | Losers retained inside `ResolvedValue` as `losers: [Match]`; public API, because `explain` and IR v2 both need them. |
| D3 | `explain` command address: `<selector> [property]` positional (avoids `Node.prop` ambiguity with class selectors). |
| D4 | SHA-256 without swift-crypto: vendor ~100-line pure-Swift implementation with NIST vectors in `Tests/`. |
| D4 | ~~SHA-256 without swift-crypto: vendor pure-Swift implementation~~ Superseded 2026-06-11 (R11): use `swift-crypto` — same CryptoKit API, Linux-capable; accepted as the second dependency. Shipped CryptoKit wrapper is interim. |
| D5 | `@contract:` block syntax (fits existing outline-reader mechanics, not `@contract Selector:` which needs new grammar). |

## PR sequence
Expand Down Expand Up @@ -122,13 +122,14 @@ New `Schema/hypercode-ir-v2.schema.json`:
`hypercode emit ... [--ir-version 1|2]` — default v2.
v1 emitter kept intact for `--ir-version 1`.

### Hashing (D4)
### Hashing (D4, superseded — see decisions table)

Vendor `Sources/Hypercode/Crypto/SHA256.swift` (~100 lines, FIPS 180-4).
Test vectors from NIST FIPS 180-4 in `Tests/SHA256Tests.swift`.
`Sources/Hypercode/Crypto/SHA256.swift` is a thin wrapper over the platform
SHA-256 (CryptoKit in this PR; switched to `swift-crypto` later in the chain
per R11). Test vectors from NIST FIPS 180-4 in `Tests/SHA256Tests.swift`.

Node hash input: deterministic JSON of `{type, class?, id?, properties: {key: value}, childHashes: []}`.
Document hash: SHA-256 of newline-joined sorted node hashes (BFS order).
Document hash: SHA-256 of newline-joined root-node hashes in document order.

### CI

Expand Down
127 changes: 127 additions & 0 deletions Schema/hypercode-ir-v2.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://0al-spec.github.io/Hypercode/schema/hypercode-ir-v2.schema.json",
"title": "Hypercode IR v2",
"description": "Hypercode resolved-graph IR v2: typed values, cascade trace (winner + losers), per-node SHA-256 hashes, context echo, resolver metadata. Contracts field is empty until HC-111.",
"type": "object",
"required": ["version", "context", "resolver", "documentHash", "nodes"],
"additionalProperties": false,
"properties": {
"version": {
"type": "string",
"const": "hypercode.ir/v2"
},
"context": {
"type": "object",
"description": "Echo of the --ctx flags supplied at resolution time.",
"additionalProperties": { "type": "string" }
},
"resolver": {
"type": "object",
"required": ["name", "version"],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"version": { "type": "string" }
}
},
"documentHash": {
"type": "string",
"description": "SHA-256 of newline-joined root-node hashes (document-level fingerprint).",
"pattern": "^[0-9a-f]{64}$"
},
"nodes": {
"type": "array",
"items": { "$ref": "#/$defs/resolvedNode" }
}
},
"$defs": {
"resolvedNode": {
"type": "object",
"required": ["type", "hash", "properties", "children"],
"additionalProperties": false,
"properties": {
"type": { "type": "string" },
"class": { "type": "string" },
"id": { "type": "string" },
"hash": {
"type": "string",
"description": "SHA-256 of the node's stable content (type, class?, id?, resolved values, child hashes).",
"pattern": "^[0-9a-f]{64}$"
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/resolvedProperty" }
},
"children": {
"type": "array",
"items": { "$ref": "#/$defs/resolvedNode" }
}
}
},
"resolvedProperty": {
"type": "object",
"required": ["value", "winner", "losers", "contracts"],
"additionalProperties": false,
"properties": {
"value": {
"description": "The winning resolved value (typed).",
"oneOf": [
{ "type": "string" },
{ "type": "integer" },
{ "type": "number" },
{ "type": "boolean" }
Comment thread
SoundBlaster marked this conversation as resolved.
]
},
"winner": { "$ref": "#/$defs/matchEntry" },
"losers": {
"type": "array",
"items": { "$ref": "#/$defs/matchEntry" }
},
"contracts": {
"type": "array",
"description": "Accumulated selector contracts (HC-111). Empty until contracts are added to .hcs files.",
"items": { "$ref": "#/$defs/contractEntry" }
}
}
},
"matchEntry": {
"type": "object",
"required": ["selector", "specificity", "order", "line", "value"],
"additionalProperties": false,
"properties": {
"selector": { "type": "string" },
"specificity": {
"type": "array",
"description": "[ids, classes, types]",
"items": { "type": "integer" },
"minItems": 3,
"maxItems": 3
},
"order": { "type": "integer" },
"line": { "type": "integer" },
"file": { "type": "string" },
"value": {
"oneOf": [
{ "type": "string" },
{ "type": "integer" },
{ "type": "number" },
{ "type": "boolean" }
]
}
}
},
"contractEntry": {
"type": "object",
"required": ["selector", "type", "required"],
"additionalProperties": false,
"properties": {
"selector": { "type": "string" },
"type": { "type": "string", "enum": ["string", "int", "float", "bool"] },
"required": { "type": "boolean" },
"min": { "type": "number" },
"max": { "type": "number" }
}
}
}
}
24 changes: 24 additions & 0 deletions Sources/Hypercode/Crypto/SHA256.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import CryptoKit

// Thin wrapper around CryptoKit.SHA256 so the rest of the library uses a
// stable internal interface. CryptoKit is available on all supported targets
// (macOS 10.15+; this package requires macOS 13).

struct SHA256Digest {
let bytes: [UInt8]

var hexString: String {
bytes.map { String(format: "%02x", $0) }.joined()
}
}

enum SHA256 {
static func hash(_ input: [UInt8]) -> SHA256Digest {
let digest = CryptoKit.SHA256.hash(data: input)
return SHA256Digest(bytes: Array(digest))
}

static func hash(utf8 string: String) -> SHA256Digest {
hash(Array(string.utf8))
}
}
Loading
Loading