Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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
1 change: 1 addition & 0 deletions .github/workflows/links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
with:
args: >-
--config .lychee.toml
--include-fragments
'**/*.md'
fail: true
format: markdown
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/main-push-release-branch-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ jobs:
with:
args: >-
--config .lychee.toml
--include-fragments
'**/*.md'
fail: true
format: markdown
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ jobs:
with:
args: >-
--config .lychee.toml
--include-fragments
'**/*.md'
fail: true
format: markdown
Expand Down
3 changes: 0 additions & 3 deletions .lychee.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ offline = true
# Resolve bare links like [Guide](docs/GUIDE) → docs/GUIDE.md
fallback_extensions = ["md", "html"]

# Check #heading fragment anchors within markdown files
include_fragments = true

# No interactive progress bar (clean output in hooks/CI)
no_progress = true

Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added root `intent` builders, the runtime-backed `Intent` noun,
`Timeline.write(intent)`, and `WriteReceipt` results with the public
`ReceiptOutcome` axis: `accepted`, `obstructed`, `conflicted`,
`underdetermined`, and `rejected`.
- Added root `reading` builders, the runtime-backed `Reading` noun,
`Timeline.read(reading)`, and receipt-bearing `ReadingResult` values for
first-use property and node-existence reads.
- Added `Timeline.draft(name)`, draft writes, `Timeline.previewJoin(draft)`,
and `Timeline.join(draft)` with join receipts for first-use speculative
workflows.

### Changed

- Added the v19 `openWarp()` product opener plus `Warp` and `Timeline`
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div align="center">
<p><strong>A Git-native runtime for shared causal history.</strong></p>
<p>Offline-first, multi-writer, deterministic, and built for provenance-aware intent writes, timeline reads, and receipts.</p>
<p><strong>Write intents. Read timelines. Keep receipts.</strong></p>
<p>Offline-first, multi-writer, deterministic, and built for provenance-aware applications.</p>
</div>

<p align="center">
Expand Down Expand Up @@ -51,11 +52,10 @@ replay-backed so callers receive complete diff and provenance data.

See [CHANGELOG.md](CHANGELOG.md) for the full in-repository release notes.

## Planned v19 API Direction
## v19 First-Use API

This is the public contract the v19 facade slices are moving toward. It is the
direction for new application code, not an endorsement of the deprecated v18
compatibility API.
This is the public contract new application code should start from. The
deprecated v18 graph-first API remains available only for migration.

```typescript
import { openWarp, intent, reading } from "@git-stunts/git-warp";
Expand All @@ -70,6 +70,10 @@ const warp = await openWarp({

const events = await warp.timeline("events");

await events.write(intent.node.add({
subject: "user:alice",
}));

const write = await events.write(intent.property.set({
subject: "user:alice",
key: "role",
Expand All @@ -80,6 +84,9 @@ const role = await events.read(reading.property({
subject: "user:alice",
key: "role",
}));

console.log(role.value);
console.log(role.receipt);
```

The v18 graph-first API remains only under `@git-stunts/git-warp/legacy`. That
Expand Down
26 changes: 9 additions & 17 deletions docs/migrations/v19/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,14 @@ const receipt = await events.write(

switch (receipt.outcome) {
case 'accepted':
console.log(receipt.patchSha);
break;
case 'obstructed':
await preserveRepairWork(receipt.repairHints);
break;
case 'conflicted':
await openConflictResolution(receipt.conflicts);
break;
case 'underdetermined':
await gatherSupport(receipt.evidence);
break;
case 'rejected':
throw new Error(receipt.reason);
console.log(receipt.reason ?? receipt.outcome);
break;
}
```

Expand Down Expand Up @@ -253,7 +249,7 @@ and joins first.
| `WarpWorldline` | root `Timeline` | public handle rename |
| `GitGraphAdapter` | `storage` `GitStorageAdapter` | graph name removed |
| `InMemoryGraphAdapter` | `storage` `MemoryStorageAdapter` | graph name removed |
| `GraphPersistencePort` | `storage` `StorageAdapter` | public storage contract |
| `GraphPersistencePort` | root `WarpStorage` for app options; `legacy` for the old port | public storage contract is facade-shaped |
| `commit((patch) => ...)` | `timeline.write(intent.*)` | receipt-returning |
| `PatchBuilder` | deprecated `legacy` | replace with intent builders |
| `PatchSession` | deprecated `legacy` | replace with receipt-returning writes |
Expand Down Expand Up @@ -288,18 +284,14 @@ const receipt = await timeline.write(intent.property.set({

switch (receipt.outcome) {
case 'accepted':
console.log(receipt.patchSha);
break;
case 'obstructed':
await repair(receipt.repairHints);
break;
case 'conflicted':
await resolve(receipt.conflicts);
break;
case 'underdetermined':
await gatherSupport(receipt.evidence);
break;
case 'rejected':
throw new Error(receipt.reason);
console.log(receipt.reason ?? receipt.outcome);
break;
}
```

Expand All @@ -313,8 +305,8 @@ underdetermined
rejected
```

Operation names such as `join`, `sync`, and `read` belong in
`receipt.operation`, not in `receipt.outcome`.
Operation names such as `join`, `sync`, and `read` belong in distinct receipt
classes or operation fields, not in `receipt.outcome`.

## Suggested Upgrade Sequence

Expand Down
81 changes: 34 additions & 47 deletions docs/topics/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ const warp = await openWarp({

const timeline = await warp.timeline('events');

await timeline.write(intent.node.add({
subject: 'user:alice',
}));

const write = await timeline.write(
intent.property.set({
subject: 'user:alice',
Expand All @@ -120,18 +124,14 @@ const write = await timeline.write(

switch (write.outcome) {
case 'accepted':
console.log(write.patchSha);
break;
case 'obstructed':
await preserveRepairWork(write.repairHints);
break;
case 'conflicted':
await openConflictResolution(write.conflicts);
break;
case 'underdetermined':
await gatherSupport(write.evidence);
break;
case 'rejected':
throw new Error(write.reason);
console.log(write.reason ?? write.outcome);
break;
}

const role = await timeline.read(
Expand Down Expand Up @@ -183,35 +183,30 @@ intent.property.set({
value: 'admin',
});

intent.entity.create({
intent.node.add({
subject: 'user:alice',
});

intent.link.add({
intent.edge.add({
from: 'user:alice',
to: 'team:ops',
label: 'memberOf',
});
```

Custom intent definitions are still important, but they should be a named
escape hatch:
Domain-specific builders can be layered by application code, but the root API
should keep returning runtime-backed `Intent` values:

```typescript
const assignRole = intent.define({
type: 'user.role.assign',
schema: {
subject: 'user:*',
payload: {
role: 'string',
},
},
});
function assignRole(subject: string, role: string) {
return intent.property.set({
subject,
key: 'role',
value: role,
});
}

await timeline.write(assignRole({
subject: 'user:alice',
payload: { role: 'admin' },
}));
await timeline.write(assignRole('user:alice', 'admin'));
```

The builder output should be a runtime-backed value, not loose shape trust.
Expand All @@ -238,14 +233,15 @@ In v18, users often need to call `prepareOpticBasis()`, capture a coordinate,
and read through `optic()`. That discipline is correct, but it should be
internal ceremony for the first-use path.

The v19 `timeline.read(reading)` flow can still lower to an optic internally:
The v19 `timeline.read(reading)` flow is the public shape. Advanced bounded
read paths can still lower readings to optics internally:

1. validate the `Reading`;
2. lower `Reading` to `Optic`;
3. prepare or check an optic basis;
4. capture the observer position;
5. execute the bounded read;
6. return `ReadingResult` and `Receipt`.
6. return `ReadingResult` and `ReadReceipt`.

Operational uncertainty should return receipt outcomes where possible.
Programmer errors, corruption, impossible states, and violated invariants may
Expand All @@ -256,22 +252,18 @@ still throw typed errors.
Receipts should become the normal control-flow object.

```typescript
const receipt = await timeline.write(assignRole);
const receipt = await timeline.write(assignRole('user:alice', 'admin'));

switch (receipt.outcome) {
case 'accepted':
console.log(receipt.patchSha);
break;
case 'obstructed':
await repair(receipt.repairHints);
break;
case 'conflicted':
await resolve(receipt.conflicts);
break;
case 'underdetermined':
await gatherMoreEvidence(receipt.evidence);
break;
case 'rejected':
throw new Error(receipt.reason);
console.log(receipt.reason ?? receipt.outcome);
break;
}
```

Expand All @@ -288,14 +280,8 @@ rejected
Do not mix operation types into outcomes. `joined`, `synced`, `staged`, and
`materialized` describe operations or states, not the outcome axis.

Use:

```text
receipt.operation
receipt.outcome
```

not one overloaded status string.
Use separate result classes (`WriteReceipt`, `ReadReceipt`, and later join
receipts), not one overloaded status string.

## Timelines, Drafts, And Joins

Expand All @@ -310,7 +296,7 @@ Strand -> speculative lane
Braid -> deterministic lane composition
```

Public speculative work should read like this:
Public speculative work reads like this:

```typescript
const draft = await timeline.draft('try-admin-role');
Expand Down Expand Up @@ -338,7 +324,7 @@ avoids boolean-trap API design and makes the two phases explicit.

## Tick And Coordinate

Use `Tick` for ergonomic public time travel:
Future ergonomic public time travel should use `Tick`:

```typescript
const tick = await timeline.tick();
Expand All @@ -351,11 +337,12 @@ const roleAtTick = await timeline.at(tick).read(
);
```

Use `Coordinate` for formal evidence and proof posture:
Use `Coordinate` for formal evidence and proof posture on advanced surfaces.
Do not make first-use application examples import coordinate machinery.

```text
receipt.coordinate
receipt.evidence.observerCoordinate
public: tick handle
advanced: coordinate evidence
```

That split lets casual users keep a simple handle while advanced users and
Expand Down
Loading
Loading