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
10 changes: 9 additions & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,12 @@ jobs:
run: python3 Examples/codegen-demo/check.py

- name: Kustomize comparison — metrics + all tenant×env targets validate
run: python3 Examples/kustomize-comparison/metrics.py --check
run: python3 Examples/kustomize-comparison/metrics.py --check

- name: Ontology backend — regenerated package matches the original
# macos-latest system Python is Homebrew-managed (PEP 668) — pip
# refuses to install into it; a throwaway venv sidesteps that.
run: |
python3 -m venv /tmp/ontology-venv
/tmp/ontology-venv/bin/pip install --quiet pyyaml
/tmp/ontology-venv/bin/python Examples/ontology-backend/backend.py --check
67 changes: 67 additions & 0 deletions DOCS/Dogfooding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Dogfooding Log (HC-121)

Hypercode adopted for real artifacts from its own ecosystem. Each entry
records what was modeled, what worked, and — the actual point — what hurt.
Friction items are numbered (F1…) and feed the workplan.

## Entry 1 — Ontology `examcalc` package (2026-06-12)

**What:** the real `DomainOntologyPackage` for `examcalc` (Ontology repo,
`SPECS/ontology/packages/examcalc/`) — 13 classes, 8 relations, 4 policies,
a 7-state machine — modeled as
[`Examples/ontology-backend/examcalc.{hc,hcs}`](../Examples/ontology-backend/)
with a consumer-side adapter regenerating the YAML from IR v2. CI compares
the result semantically against a verbatim copy of the original; it matched
on the first complete run.

### What worked

- **Selector defaults are the real product.** Four kind rules
(`.entity`/`.capability`/`.command`/`.event`) plus type-level defaults for
`Relation` and `Policy` carry everything the YAML restates per entry. The
per-node rules contain only what is actually specific.
- **`@stage[approved]`** turns `approvalStatus` from an edited field into a
resolved context; `hypercode diff` shows package approval as exactly one
affected node.
- **Contracts on a document that had none:** `card_min: int >= 0`, required
`domain`/`range`/`text` — `validate` now gates edits that previously went
straight to the consumer compiler.
- **The Backends.md boundary held.** Every piece of schema knowledge
(envelope, key spelling, list encoding) fit naturally in the adapter;
nothing leaked into core.

### Friction

- **F1 — no list values in core.** `implements`, `appliesTo`, `states`,
`oneOf` ranges and the compatibility lists travel as comma-joined strings;
the split convention is an undocumented contract between sheet and
backend. Tolerable at this size, but it is the first thing a second
consumer would re-invent differently. *Candidate: list scalars as a core
extension or a sanctioned dialect layer (M9 discussion; conflicts with
"core stays minimal" — needs a decision, not a default).*
- **F2 — contract types are single-typed.** The schema's
`cardinality.max: int | "*"` is inexpressible; `card_max` ships
unconstrained. *Candidate: union types or value-pattern constraints in the
contract grammar.*
- **F3 — synthetic sibling ids.** Same-type siblings (the five `Transition`
nodes) need invented ids (`#start`, `#verify`, …) purely to be
addressable. Honest cost of the anchor model; the invented names did turn
out useful in `explain`/diff output.
- **F4 — id-selector quoting noise.** Every per-node rule reads
`'#Exam':` — the quotes (because bare `#` opens a comment) are the most
common syntax error while writing the sheet.
- **F5 — flat property names.** Nested YAML keys (`metadata.id`,
`cardinality.min`) flatten to `package_id`, `card_min`; the mapping lives
in the backend. Correct per the layering rules, but it means the sheet and
the target document drift vocabularies — provenance bridges it, a naming
convention would help.

### Verdict

Parity on size for one package (201 vs 212 meaningful lines), clear wins on
defaults, lifecycle-as-context, diff, contracts and provenance. The
compression story starts at the second package, when the kind defaults and
contracts move to a shared `@import`ed baseline. Next adoption step
(remaining scope of HC-121): an `import-hypercode` step inside `ontologyc`
itself, consuming the IR the way `backend.py` does, and the same exercise
for a Hyperprompt configuration.
97 changes: 97 additions & 0 deletions Examples/ontology-backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# HC-121 — Dogfooding: a real Ontology package through the IR

The worked example of [DOCS/Backends.md](../../DOCS/Backends.md), runnable.
The **real** `examcalc` DomainOntologyPackage from the Ontology repo —
13 classes, 8 relations, 4 policies, a 7-state machine, 240 lines of
hand-written YAML — described as `examcalc.{hc,hcs}` and regenerated through
the resolved IR by a consumer-side adapter:

```
examcalc.hc + examcalc.hcs ──▶ hypercode emit (IR v2) ──▶ backend.py ──▶ DomainOntologyPackage YAML
semantically compared in CI against
expected/ — a verbatim copy of the
Ontology repo's hand-written file
```

```console
$ python3 backend.py --check
generated DomainOntologyPackage is semantically identical to the Ontology repo original (240 lines of YAML)
```

All schema knowledge — envelope constants, key names, the comma-joined list
encoding — lives in [`backend.py`](backend.py), per the Backends rule:
*Hypercode never learns the ontology schema*.

## Where the cascade earns its keep

The YAML repeats per entry what the sheet states once per **kind**:

```hcs
.entity:
extends: "sg:DomainEntity" # covers 7 classes

.command:
extends: "sg:Command" # covers 4

Relation:
card_min: 0 # covers 5 of 8 relations;
card_max: "*" # the three 1..1 pairs override

Policy:
extends: "sg:Policy"
enforceability: "runtime" # covers all 4
```

And every derived value stays explainable:

```console
$ hypercode explain examcalc.hc --hcs examcalc.hcs "'#ExamSession'" extends
Node: Package#examcalc > Classes > Class.entity#ExamSession
extends
WINNER .entity { value: sg:DomainEntity }
file: examcalc.hcs line: 8 specificity: (0,1,0) order: 0
```

## Context and diff on a real document

`approvalStatus` is a lifecycle value, not data — so it is a context:

```console
$ hypercode emit examcalc.hc --hcs examcalc.hcs > draft.ir.json
$ hypercode emit examcalc.hc --hcs examcalc.hcs --ctx stage=approved > approved.ir.json
$ hypercode diff draft.ir.json approved.ir.json
~ Package#examcalc > Metadata
~ approval_status: draft → approved
was: Metadata @ examcalc.hcs:30
now: Metadata @ examcalc.hcs:39

1 affected node(s)
```

One affected node — approving a package invalidates exactly the artifacts
derived from its metadata, nothing else. Contracts gate edits the same way
they gate the other examples: `card_min: -1` or a missing relation `domain`
fails `hypercode validate` before the package ever reaches `ontologyc`.

## The honest part

For a *single* package the size is parity, not victory: 201 meaningful spec
lines vs 212 meaningful YAML lines. The compression argument starts at the
**second** package, when `.entity`/`.command` defaults and the contracts move
to a shared baseline imported by every package sheet (`@import`, HC-116) —
the same shape as the [Kustomize comparison](../kustomize-comparison/)'s
tenant sheets. What a single package gains today is not size: it is selector
defaults, per-context lifecycle, semantic diff, contract gating and
provenance on a document that previously had none of those.

Everything that hurt while writing this is recorded in
[DOCS/Dogfooding.md](../../DOCS/Dogfooding.md) — the friction log is the
deliverable dogfooding exists to produce.

| File | Role |
|---|---|
| `examcalc.hc` | the package topology (42 meaningful lines) |
| `examcalc.hcs` | kind defaults + per-node specifics + `@stage` + contracts |
| `backend.py` | consumer adapter: IR v2 → DomainOntologyPackage; `--check` for CI |
| `expected/domain-ontology-package.yaml` | verbatim copy of the Ontology repo original |
Loading
Loading