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
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- run: npm install --prefix docs
- run: npm run --prefix docs build
- uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.162.1'
extended: true
- run: npm run docs:build
10 changes: 6 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- run: npm install --prefix docs
- run: npm run --prefix docs build
- uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.162.1'
extended: true
- run: npm run docs:build
- uses: actions/upload-pages-artifact@v3
with:
path: docs/dist
path: docs/public

deploy:
needs: build
Expand Down
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ dist/
*.tsbuildinfo
*.tgz
.DS_Store
docs/.astro/
docs/dist/
docs/public/
docs/resources/
docs/.hugo_build.lock
# generated from spec/*.md by docs/scripts/sync-spec.mjs
docs/src/content/docs/spec/
docs/content/spec/normative/
# copied from spec/fixtures at build time
packages/conformance/fixtures/
packages/conformance/spec/
Expand All @@ -16,4 +17,4 @@ packages/*/src/**/*.js.map
packages/*/src/**/*.d.ts
packages/*/src/**/*.d.ts.map
# generated by docs/scripts/sync-spec.mjs from the repository's artifacts
docs/src/data/figures.json
docs/data/figures.json
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The repository is three things and a site.
| `spec/fixtures/` | Conformance fixtures, one directory per rule |
| `packages/reference` | The reference implementation (`@intentius/tsad-reference`) |
| `packages/conformance` | The adapter interface, fixture format, runner, and the chant adapter |
| `docs/` | The published site (Astro + Starlight), generated from `spec/` |
| `docs/` | The published site (Hugo, on choudoufu's layout); the normative pages are generated from `spec/` |

## Relationship to chant

Expand All @@ -35,8 +35,8 @@ npm test
npm run lint:prose
TSAD_CHANT_REPO=../chant npm run corpus # the corpus cross-check (#25); needs a chant checkout with its lexicons generated

npm run docs # docs dev server
npm run docs:build # static build, as CI runs it
npm run docs # docs dev server (needs hugo)
npm run docs:build # static build to docs/public, as CI runs it
```

## License
Expand Down
246 changes: 246 additions & 0 deletions docs/assets/css/site.css

Large diffs are not rendered by default.

38 changes: 0 additions & 38 deletions docs/astro.config.mjs

This file was deleted.

36 changes: 36 additions & 0 deletions docs/content/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: "typescript-as-data"
description: "TypeScript in place of YAML. A file that is data is typed JSON, and it means the same thing whether it is folded or run."
---

## The same policy twice

```yaml {title="governance.yml"}
orgs:
my-org:
repos:
api:
hasWiki: false
allowSquashMerge: true
topics: [service, api]
branchProtection:
- ruleName: main
requiredApprovals: 1
enableStatusCheck: true
statusCheckContexts: [ci]
```

```ts {title="governance.ts"}
import type { GovernanceConfig } from "@intentius/forgejo-warden";
import { service } from "./helpers.ts";

export const policy = {
orgs: {
"my-org": {
repos: { api: service("api"), web: service("web") },
},
},
} satisfies GovernanceConfig;
```

The second one has completion and a type error on a misspelt key. It reuses one helper across repositories. The tool reads the same object from either file and never runs the second one to get it.
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
---
title: Add it to your platform
description: What a tool supplies to accept .ts in place of .yml, and what judges it.
title: "Add it to your platform"
description: "What a tool supplies to accept .ts in place of .yml, and what judges it."
weight: 30
diataxis: how-to
sidebar:
order: 1
hideChildren: true
---

import consumers from "../../../data/consumers.json";

Your tool reads a YAML file. To read a TypeScript file instead, you supply four things, and the last two are optional.

1. Types for your schema, as a `.d.ts` your users import and `satisfies`. If you have a JSON Schema, this is generated. If you have TypeScript interfaces already, export them.
Expand All @@ -19,30 +17,18 @@ That is the whole cost. The evaluator is a package; the types are yours; nothing

## The profile you implement

An evaluator with no JavaScript runtime implements the data-host profile: expression evaluation, the per-file verdict with a failed fold as an error rather than a fallback, envelopes as the output, no helpers, no live constructors. It is `F-Profile-DataHost` in [the judgments](/typescript-as-data/spec/judgments/), a table of subtractions from the full specification, and the reference with an empty host is judged on it.
An evaluator with no JavaScript runtime implements the data-host profile: expression evaluation, the per-file verdict with a failed fold as an error rather than a fallback, envelopes as the output, no helpers, no live constructors. It is `F-Profile-DataHost` in [the judgments](/typescript-as-data/spec/normative/judgments/), a table of subtractions from the full specification, and the reference with an empty host is judged on it.

## What judges you

The conformance suite, `@intentius/tsad-conformance`: the fixtures at the spec version you declare, a runner, and an adapter interface. You supply an adapter; you get a report. [How the fixtures work](/typescript-as-data/conformance/fixtures/) and [the coverage gate](/typescript-as-data/conformance/coverage/) are under the specification door.
The conformance suite, `@intentius/tsad-conformance`: the fixtures at the spec version you declare, a runner, and an adapter interface. You supply an adapter; you get a report. [How the fixtures work](/typescript-as-data/spec/conformance/fixtures/) and [the coverage gate](/typescript-as-data/spec/conformance/coverage/) are under the specification door.

## Evaluators in other languages

The reference is TypeScript. A Rust evaluator on oxc is [issue #86](https://github.com/INTENTIUS/typescript-as-data/issues/86), after the profile and the coercion fixtures exist. As WASM it would embed without a subprocess in Go and Python, in the browser and in an editor. It is not "rust-as-data"; the language a user writes is TypeScript whatever evaluates it.

## Who has done it

<table>
<thead><tr><th>Consumer</th><th>What it is</th><th>Status</th><th>Note</th></tr></thead>
<tbody>
{consumers.map((c) => (
<tr>
<td><a href={c.link}>{c.name}</a></td>
<td>{c.kind}</td>
<td>{c.status}</td>
<td>{c.note}</td>
</tr>
))}
</tbody>
</table>
{{< consumers >}}

Each consumer page has the same four sections: how a user authors and how the tool checks, then what it generates back and what proves the agreement.
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
---
title: chant
description: The infrastructure toolchain the specification was extracted from, and the reference platform for everything it enables.
title: "chant"
description: "The infrastructure toolchain the specification was extracted from, and the reference platform for everything it enables."
weight: 3
diataxis: how-to
sidebar:
order: 3
---

import figures from "../../../data/figures.json";

[chant](https://intentius.io/chant/) is typed infrastructure end to end, TypeScript in and spec-native output out, with the lifecycle (observe, reconcile, apply) built on top of a synthesis core that runs no project code. The specification here was extracted from its fold mechanism and now owns it; chant implements the specification and declares which version.

## Author
Expand All @@ -24,12 +21,12 @@ Three generators share one pipeline. `chant import` reads an existing template,

## Proof

chant {figures.chantPin} passes every fixture it can answer and agrees with the reference implementation on every one. Over chant's own example corpus the two implementations agree on every comparable file, and produce identical export namespaces wherever both fold.
chant {{< figure "chantPin" >}} passes every fixture it can answer and agrees with the reference implementation on every one. Over chant's own example corpus the two implementations agree on every comparable file, and produce identical export namespaces wherever both fold.

| Over chant's example corpus | Count |
|---|---|
| Projects and files at <code>{figures.corpus.revision}</code> | {figures.corpus.entries} projects, {figures.corpus.files} files |
| Comparable, all agreed | {figures.corpus.comparable} |
| Both fold, namespaces identical | {figures.corpus.bothFold} |
| Projects and files at <code>{{< figure "corpus.revision" >}}</code> | {{< figure "corpus.entries" >}} projects, {{< figure "corpus.files" >}} files |
| Comparable, all agreed | {{< figure "corpus.comparable" >}} |
| Both fold, namespaces identical | {{< figure "corpus.bothFold" >}} |

The limits that make the other files incomparable are counted rather than hidden, in [the corpus cross-check](/typescript-as-data/conformance/corpus/).
The limits that make the other files incomparable are counted rather than hidden, in [the corpus cross-check](/typescript-as-data/spec/conformance/corpus/).
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
---
title: forgejo-warden
description: Org governance for Forgejo, declared as data. The first consumer that is neither this repository nor chant.
title: "forgejo-warden"
description: "Org governance for Forgejo, declared as data. The first consumer that is neither this repository nor chant."
weight: 2
diataxis: how-to
sidebar:
order: 2
---

[forgejo-warden](https://github.com/INTENTIUS/forgejo-warden) keeps a Forgejo org and its repos in a declared state. It diffs against live and applies guardrails. Runs are dry by default, and deletes happen only where the policy says `owned`. Its policy is one file, and the `.ts` form of that file is [forgejo-warden#33](https://github.com/INTENTIUS/forgejo-warden/pull/33).
Expand Down
8 changes: 8 additions & 0 deletions docs/content/spec/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: "The specification"
description: "The normative text, the reference implementation that implements it, the conformance suite that judges an implementation, and the evidence behind every figure."
weight: 40
aliases: ["/introduction/"]
---

Four parts, in reading order. The introduction says what the document set is and how its rules are named. The normative text is the specification itself, generated from `spec/*.md` in the repository at build time. The reference implementation is the evaluator the packages ship, and conformance is what judges it, or yours.
6 changes: 6 additions & 0 deletions docs/content/spec/conformance/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: "Conformance"
description: "The fixtures, the coverage gate and the two cross-checks against chant."
weight: 4
---

Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
---
title: The chant cross-check
description: Running the fixtures against chant through its public fold API, pinned to a release.
sidebar:
order: 4
title: "The chant cross-check"
description: "Running the fixtures against chant through its public fold API, pinned to a release."
weight: 4
aliases: ["/conformance/chant-cross-check/"]
---

import figures from "../../../data/figures.json";

`packages/conformance/src/chant-agreement.test.ts` runs the fixture set against
chant, and then runs both implementations against each other.

The pin is a real dependency. `@intentius/chant` is in this repository's
`devDependencies` at version <code>{figures.chantPin}</code>, and the adapter in
`devDependencies` at version <code>{{< figure "chantPin" >}}</code>, and the adapter in
`packages/conformance/src/adapters/chant.ts` reaches it through its public
entry only: `fold`, `collectConsts`, `FoldError`, and, since `chant-v0.64.0`,
`findSubsetViolation` for the shape half. No source is vendored and no internal
Expand Down Expand Up @@ -53,7 +51,7 @@ is asserted rather than assumed.

| Pin | Fixtures | Rules with a fixture | Shape and fold agreement |
|---|---|---|---|
| chant <code>{figures.chantPin}</code> | {figures.fixtures}, of which {figures.wholeBuildFixtures} are whole-build | {figures.rulesWithFixture} of {figures.rulesTotal} | all, on every fixture the pin can answer |
| chant <code>{{< figure "chantPin" >}}</code> | {{< figure "fixtures" >}}, of which {{< figure "wholeBuildFixtures" >}} are whole-build | {{< figure "rulesWithFixture" >}} of {{< figure "rulesTotal" >}} | all, on every fixture the pin can answer |

The counts are read from the fixture tree at build time; the agreement figure
is `paper/measurements.md`'s.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
---
title: The corpus cross-check
description: Running chant's whole example corpus through both implementations, the two named limits, and what the numbers mean.
sidebar:
order: 5
title: "The corpus cross-check"
description: "Running chant's whole example corpus through both implementations, the two named limits, and what the numbers mean."
weight: 5
aliases: ["/conformance/corpus/"]
---

import figures from "../../../data/figures.json";

`packages/conformance/src/corpus.ts` runs chant's example corpus through both
implementations and compares verdicts file by file, and export namespaces where
both folded. It is issue #25.
Expand Down Expand Up @@ -93,21 +91,21 @@ From `paper/measurements.md`:

| Corpus | Files | Comparable | Agreed | Both fold, namespaces identical |
|---|---|---|---|---|
| chant <code>{figures.corpus.corpusVersion}</code> at <code>{figures.corpus.revision}</code>, {figures.corpus.entries} entries | {figures.corpus.files} | {figures.corpus.comparable} | {figures.corpus.agreed} | {figures.corpus.bothFold} |
| chant <code>{{< figure "corpus.corpusVersion" >}}</code> at <code>{{< figure "corpus.revision" >}}</code>, {{< figure "corpus.entries" >}} entries | {{< figure "corpus.files" >}} | {{< figure "corpus.comparable" >}} | {{< figure "corpus.agreed" >}} | {{< figure "corpus.bothFold" >}} |

And the limits, from the same file: {figures.corpus.noComposite} files reach a
And the limits, from the same file: {{< figure "corpus.noComposite" >}} files reach a
composite factory call, a form the reference does not implement, and
{figures.corpus.noHost} imports a package the host cannot load.
{{< figure "corpus.noHost" >}} imports a package the host cannot load.

`paper/measurements.md` states what this establishes: on
{figures.corpus.comparable} files nobody wrote for the purpose, the two
implementations agree on every verdict, and on the {figures.corpus.bothFold}
{{< figure "corpus.comparable" >}} files nobody wrote for the purpose, the two
implementations agree on every verdict, and on the {{< figure "corpus.bothFold" >}}
that fold on both sides the export namespaces are structurally identical,
entity class and properties included. It is the first agreement between the two
that was observed rather than designed.

It also states the limit, and the limit is the more useful half.
{figures.corpus.comparable} of {figures.corpus.files} is the comparable set, not the corpus, and every limit is an over-approximation,
{{< figure "corpus.comparable" >}} of {{< figure "corpus.files" >}} is the comparable set, not the corpus, and every limit is an over-approximation,
so a file under one may also be hiding a disagreement. The composite limit
alone removes two thirds of the corpus, and the most common form in real
projects is therefore the one the cross-check cannot yet speak to.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
---
title: Coverage and the uncovered list
description: The two-directional coverage gate, and what spec/fixtures/UNCOVERED.md is for.
sidebar:
order: 3
title: "Coverage and the uncovered list"
description: "The two-directional coverage gate, and what spec/fixtures/UNCOVERED.md is for."
weight: 3
aliases: ["/conformance/coverage/"]
---

import figures from "../../../data/figures.json";

One of the process rules in `spec/README.md` is that every identifier is
exercised by at least one fixture, and every fixture cites a real identifier,
in both directions, in CI. `spec/fixtures.test.ts` is that gate.
Expand Down Expand Up @@ -56,8 +54,8 @@ rule identifier in backticks, an em dash, and a reason. `fixtures.test.ts`
parses exactly that shape with a regular expression, so an entry written any
other way is not an exemption and the gate will still fail on the rule.

The file's own header states the count, {figures.rulesWithFixture} of
{figures.rulesTotal} rules with a fixture.
The file's own header states the count, {{< figure "rulesWithFixture" >}} of
{{< figure "rulesTotal" >}} rules with a fixture.

`paper/measurements.md` sorts the rest by what each waits on. Five need a
form the reference implementation lacks, the composite factory and isolation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: The fixture format
description: The two kinds of conformance fixture, expression and project, with an example of each from spec/fixtures/.
sidebar:
order: 2
title: "The fixture format"
description: "The two kinds of conformance fixture, expression and project, with an example of each from spec/fixtures/."
weight: 2
aliases: ["/conformance/fixtures/"]
---

A fixture is a directory under `spec/fixtures/<Rule>/<name>/`. The format is
Expand All @@ -12,7 +12,7 @@ contains.

Every fixture's `expect.json` carries a `rules` array naming the identifiers
the fixture exercises. That array is what the
[coverage gate](/typescript-as-data/conformance/coverage/) reads in both
[coverage gate](/typescript-as-data/spec/conformance/coverage/) reads in both
directions, so it is not decoration. Most carry a `note` saying why the fixture
exists.

Expand Down
Loading