Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
778acb0
feat: add entity.add for dependency-pure entity capture
flyingrobots Aug 1, 2026
8309c7c
fix: make entity capture prove what it claims
flyingrobots Aug 2, 2026
d85e1c6
docs: target 20.0.0 for the entity capture union widening
flyingrobots Aug 2, 2026
6ee206a
docs: correct entity capture uniqueness to what the lane path keeps
flyingrobots Aug 2, 2026
d2affdf
feat: expose causal entity occurrences
flyingrobots Aug 3, 2026
f303500
docs: refresh causal occurrence reference
flyingrobots Aug 3, 2026
2ce87ef
Fix: preserve allocated subjects through settlement
flyingrobots Aug 3, 2026
46de620
Fix: scope entity occurrences to worldlines
flyingrobots Aug 3, 2026
7dd52ec
Fix: reject unsafe causal counters
flyingrobots Aug 3, 2026
a1f4565
Fix: guard entity writes after commit
flyingrobots Aug 3, 2026
28e7332
Fix: restore PatchBuilder source budget
flyingrobots Aug 3, 2026
1d15989
Fix: reject forged entity occurrences
flyingrobots Aug 3, 2026
87dfd84
Fix: remove entity domain type assertions
flyingrobots Aug 3, 2026
25f6057
Fix: distinguish occurrence receipt surfaces
flyingrobots Aug 3, 2026
778b998
Fix: validate published entity captures
flyingrobots Aug 3, 2026
0211145
Fix: exclude conflict entity occurrences
flyingrobots Aug 3, 2026
0f3ae02
Fix: reject non-record entity payloads
flyingrobots Aug 3, 2026
9bdc545
Fix: ignore undefined entity identities
flyingrobots Aug 3, 2026
896de8a
Fix: describe safe Dot counters
flyingrobots Aug 3, 2026
2592eec
Fix: isolate CLI documentation checks
flyingrobots Aug 3, 2026
5fc6ea0
Fix: align Dot codec diagnostic
flyingrobots Aug 3, 2026
91872a2
Fix: reject non-record Intent payloads
flyingrobots Aug 4, 2026
70ee640
Fix: bind published entity payloads
flyingrobots Aug 4, 2026
5280442
Fix: bind occurrences to write receipts
flyingrobots Aug 4, 2026
481a729
Fix: enforce assertion-free entity tests
flyingrobots Aug 4, 2026
46efde4
Fix: separate receipt and causal writers
flyingrobots Aug 4, 2026
affbef1
Fix: cover entity payload comparison branches
flyingrobots Aug 4, 2026
45a2c01
Fix: preserve publication coordinates in tests
flyingrobots Aug 4, 2026
1a0a383
Fix: bind occurrences to canonical evidence
flyingrobots Aug 4, 2026
62d26d2
Fix: ratchet payload comparison sources
flyingrobots Aug 4, 2026
776d0b4
Fix: cover canonical evidence normalization
flyingrobots Aug 4, 2026
dc66709
Fix: bind allocated publication subjects
flyingrobots Aug 4, 2026
d4147d5
Fix: move occurrence authority into the domain object
flyingrobots Aug 4, 2026
781b0f1
Fix: derive evidence canonicality from structure
flyingrobots Aug 4, 2026
4889004
Fix: distinguish declared and semantic dependencies
flyingrobots Aug 4, 2026
3162c64
Fix: reject noncanonical Dot encodings
flyingrobots Aug 4, 2026
79a112e
Fix: preserve prototype-named vector writers
flyingrobots Aug 4, 2026
9b89f5c
Fix: access serialized vector keys explicitly
flyingrobots Aug 4, 2026
51c75fe
Fix: remove entity payload completeness claims
flyingrobots Aug 4, 2026
d37bf01
Fix: split PatchBuilder property operations
flyingrobots Aug 4, 2026
a78b91f
Fix: format entity capture changes
flyingrobots Aug 4, 2026
0b26f2e
Fix: keep occurrence issuance out of public surface
flyingrobots Aug 4, 2026
989c56f
Fix: remove unknown from PatchBuilder values
flyingrobots Aug 4, 2026
605f221
Fix: restore generated reference formatting
flyingrobots Aug 4, 2026
c6ad3d2
Fix: stabilize oversized migration coverage proof
flyingrobots Aug 4, 2026
f8536fb
Fix: remove vacuous occurrence subject check
flyingrobots Aug 4, 2026
5cc4897
Fix: validate canonical retention evidence
flyingrobots Aug 4, 2026
de598b1
Test: reject entity capture without identity
flyingrobots Aug 4, 2026
e2d4ec9
Fix: fence staged content after commit
flyingrobots Aug 4, 2026
006a045
Fix: install Wesley from crates.io
flyingrobots Aug 4, 2026
4063ff9
Fix: reject machine-local path leaks
flyingrobots Aug 4, 2026
5f24b62
Fix: detect case-variant Windows home paths
flyingrobots Aug 4, 2026
cc9f85e
Fix: scan exact staged content for path leaks
flyingrobots Aug 4, 2026
9e654c6
Fix: inspect binary blobs for path leaks
flyingrobots Aug 4, 2026
181cad1
Fix: block outgoing Git object path leaks
flyingrobots Aug 4, 2026
c1efd1d
Fix: require exact-tree path hygiene in CI
flyingrobots Aug 4, 2026
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
21 changes: 18 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ jobs:
- name: 'Gate 4c: Anti-sludge shell checks (junk-drawer filenames)'
run: npm run lint:sludge

type-firewall-path-hygiene:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- name: Use Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version: '22'
- name: 'Gate 4p: Exact committed-tree machine-path hygiene'
run: node scripts/check-machine-local-paths.ts --tree "$GITHUB_SHA"

type-firewall-semgrep:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -127,10 +140,9 @@ jobs:
- name: 'Gate 5a: Install pinned Wesley generator'
run: >-
cargo install
--git https://github.com/flyingrobots/wesley.git
--rev 4891a631f888c5b2f70e117e3704538dd1362c2f
--locked
wesley-cli
--version 0.3.0-alpha.1
--locked
- name: 'Gate 5b: Wesley vocabulary IR drift'
run: npm run check:vocabulary-ir
- name: 'Gate 5c: v19 capability artifact drift'
Expand Down Expand Up @@ -184,6 +196,7 @@ jobs:
needs:
- type-firewall-types
- type-firewall-lint
- type-firewall-path-hygiene
- type-firewall-semgrep
- type-firewall-quarantine
- type-firewall-surface
Expand All @@ -195,6 +208,7 @@ jobs:
run: |
echo "types: ${{ needs['type-firewall-types'].result }}"
echo "lint: ${{ needs['type-firewall-lint'].result }}"
echo "paths: ${{ needs['type-firewall-path-hygiene'].result }}"
echo "semgrep: ${{ needs['type-firewall-semgrep'].result }}"
echo "quarantine: ${{ needs['type-firewall-quarantine'].result }}"
echo "surface: ${{ needs['type-firewall-surface'].result }}"
Expand All @@ -203,6 +217,7 @@ jobs:

test "${{ needs['type-firewall-types'].result }}" = "success"
test "${{ needs['type-firewall-lint'].result }}" = "success"
test "${{ needs['type-firewall-path-hygiene'].result }}" = "success"
test "${{ needs['type-firewall-semgrep'].result }}" = "success"
test "${{ needs['type-firewall-quarantine'].result }}" = "success"
test "${{ needs['type-firewall-surface'].result }}" = "success"
Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ DTO and stop there**. Do not hallucinate fake domain models.
- NEVER force any git operation.
- NEVER use destructive cleanup or history rewrite commands like `git reset --hard`, `git clean -f`, `git checkout .`, or `git restore .`.
- This repo stores graph data as Git commits; rewriting history can destroy user data.
- NEVER publish machine-local absolute paths. This applies to tracked files,
generated evidence, PR and issue bodies, comments, and reviews. Use
repository-relative paths, `~`, `$HOME`, or an explicit placeholder such as
`<HOME>`.
- At the end of each turn, stage only the specific files written in that turn. Do not use `git add -A` by default.
- If you wrote files in the turn, commit them in that turn. Do not leave your own edits staged but uncommitted.
- Cycle-start draft pull requests are allowed and expected. After the design
Expand Down
154 changes: 154 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,160 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `intent.entity.add({ subject, properties })` creates one entity occurrence and
its initial payload in a single patch. The lowered patch declares an empty
read set and exactly one subject write. That declaration describes the
operands encoded by the patch; it does not prove that the caller made no
prior graph read to choose the subject or payload. An application-supplied
semantic subject may intentionally receive more than one occurrence.
Previously the only
way to create a node with properties was `node.add` followed by
`property.set`, which costs two patches and records a self-read on the payload
patch.
- `PatchBuilder.addEntity(nodeId, properties)` lowers that intent. It requires a
non-empty payload (`E_PATCH_ENTITY_EMPTY`) and refuses an id the builder can
already see (`E_PATCH_ENTITY_EXISTS`).
- `intent.entity.addAuto({ namespace, properties })` and the CLI's
`entity.add` namespace form allocate an opaque subject from the same
writer-local dot used by `NodeAdd`. Applications without an independent
semantic key no longer need to mint a timestamp/counter tuple or maintain a
shadow writer counter.
- Admitted entity `WriteReceipt`s carry an `EntityOccurrence` with the resolved
subject and opaque occurrence id. `relationTo` uses version-vector context for
`before`/`after`/`concurrent`; `compare` uses the canonical `EventId` order for
deterministic listing. Application timestamps remain payload metadata only.
- `intentFromPatch` recovers an entity capture from its persisted evidence: a
leading `NodeAdd`, property writes on that same node, no repeated key, and a
recorded footprint of no reads and exactly one write naming the created
subject. A patch that merely resembles the shape without declaring that
footprint is not recognised; it falls through to the one-operation rule and
fails hydration rather than being promoted to a stronger claim.

Payload keys are ordered canonically at both ends, so two payloads differing
only in construction order produce identical operations. Property maps are
built with a null prototype, so a key such as `__proto__` stays ordinary data.

Scope of these guarantees, stated because the shape is easy to over-read:
- **Initial payload, not complete entity.** Which fields make an entity
complete is an application schema concern; the substrate checks only that
properties exist.
- **Creation, not lifetime.** An auto-allocated subject's cone is a singleton
until something else writes the id. `property.set` and `node.remove` remain
available, so an immutable-entity lifetime is a law an application adopts,
not one this constructor imposes.
- **No uniqueness on the lane path.** `E_PATCH_ENTITY_EXISTS` fires only for
an id the builder can see: added earlier in the same patch, or alive in the
materialized basis it was opened against. A `Runtime` lane writer has
neither — nothing on `Runtime` materializes, and one intent lowers to one
patch — so the guard never fires there. Re-creating a subject is admitted
on one lane by one writer, across writers sharing a frontier, and by a
writer opening after the first creation is durable; the join merges them
into one entity with a multi-patch cone. The guard is a mistake-catcher for
a directly constructed `PatchBuilder` opened against a materialized state.
Supplied semantic-subject uniqueness remains the application's invariant;
substrate allocation is available when no such semantic key exists. Every
admitted addition still has a distinct substrate occurrence.

### Changed

- Repository lint now rejects personal-home and Darwin temporary absolute
paths in tracked or unignored text and binary files, and the pre-commit hook
inspects exact staged additions and modifications rather than mutable
working-tree bytes. The pre-push hook inspects every outgoing Git object, so a
later safe branch tip cannot conceal an earlier leaking blob or commit.
A dedicated required CI lane independently inspects the exact commit tree
before merge. Contributor doctrine also forbids publishing machine-local paths
in generated evidence, issues, pull requests, comments, or reviews.
- Node and edge content attachment share one staging helper, so the asset
storage precondition is stated once instead of duplicated per target shape.
- Effect id validation and derivation moved to `PatchBuilderValidation`.
- Permissive `PatchBuilder` effect and property values now use method type
parameters instead of leaking boundary-level `unknown` into the domain API.
Runtime validation and accepted JavaScript inputs are unchanged.

### Fixed

- Content attachment now rechecks the builder lifecycle after asynchronous
asset staging. Publication that overtakes staging can no longer be followed
by late property operations or attachment handles on an already committed
patch.
- Live strand settlement now replays the canonical Intent recovered from the
published draft patch. An auto-allocated entity therefore keeps the subject
named by its write receipt, matching settlement after a Runtime reopen.
- Entity occurrence identity and ordering now include their worldline scope.
Equal writer dots in independent worldlines are concurrent instead of being
misreported as the same occurrence; cross-worldline lists order the worldline
before applying canonical `EventId` order.
- Dots and version-vector counters now reject integers beyond JavaScript's exact
range. `Dot.decode` also rejects counter spellings that `Dot.encode` cannot
produce, so suffixes, decimal points, exponents, signs, leading zeroes, and
whitespace cannot alias a canonical Dot. Counter exhaustion fails before
mutation instead of reissuing a writer Dot and colliding an auto-allocated
entity subject.
- Version-vector serialization now preserves prototype-named writers such as
`__proto__` as own data properties instead of invoking inherited object
setters and silently dropping their causal coordinates.
- `PatchBuilder.addEntity` now enforces the committed-builder lifecycle before
reading snapshot state or validating entity input, matching every other
builder mutation.
- Admitted entity receipts now require the occurrence-owned causal coordinate,
bound to the exact Intent, causal Evidence, lane, and writer for which it was
issued. The runtime adapter retains no ambient occurrence registry, and the
domain object owns its comparison and relation behavior instead of accepting
injected callbacks. Neither an arbitrary `EntityOccurrence` instance nor a
genuine occurrence transplanted from another receipt can forge authoritative
receipt identity. Receipt binding records that public writer separately from
the Dot/EventId writer, so a strand overlay remains a valid causal coordinate
without impersonating the receipt writer. Evidence canonicalization is
structurally idempotent without a process-local membership registry, and
occurrence validation uses the exact frozen Evidence exposed by the receipt,
so the genuine public pair remains self-authenticating after construction.
- Canonical retention evidence now revalidates policy, reachability, and root
kind before preserving object identity. A frozen forged prototype cannot
bypass the same field invariants enforced by `RetentionEvidence` construction.
- Entity occurrence issuance now hydrates the complete published patch as an
entity capture and binds every normalized payload value, plus any supplied
subject, back to the requested Intent. A publication callback cannot
substitute an unrelated node or payload while retaining the original receipt
Intent.
- Auto-allocated entity receipts now bind the published subject to the
requested namespace and the published `NodeAdd` Dot. A publication callback
cannot substitute another subject while retaining the legitimate causal
coordinate.
- Entity conflict receipts no longer require or accept an occurrence. Only
`derived` and `plural` outcomes identify admitted entity writes.
- Entity Intent construction and direct PatchBuilder capture now share one
payload-record boundary: both reject arrays, primitives, and class instances.
Only plain or null-prototype property records are admitted.
- Entity Intent identity selection treats explicitly `undefined` optional
`subject` or `namespace` fields as absent, matching ordinary optional-field
semantics for spread-built inputs.
- Invalid Dot counters now report the enforced positive-safe-integer constraint
instead of describing the weaker integer-only rule.

### Breaking

- **`entity.add` widens the `Intent` discriminated union.** `IntentKind` and
`IntentDescriptor` are not exported by name, but both are structurally
reachable through `Intent['kind']` and `Intent['descriptor']`, so a consumer
that switches exhaustively over intent kinds stops compiling. Measured against
this branch's published surface:

```text
error TS2345: Argument of type '"entity.add"'
is not assignable to parameter of type 'never'.
```

The runtime surface is purely additive, and this repository's own consumer
contract (`test/type-check`) still compiles because it does not switch
exhaustively. The type-level break is nonetheless real for any consumer that
opted into exhaustiveness checking, so this release targets **20.0.0**.

Migration: add a `case 'entity.add':` arm, or stop treating the union as
closed.

## [19.0.2] - 2026-07-29

### Release notes
Expand Down
Loading
Loading