# Needs, not vendors

> An Endoskeletal specification says what the organization needs and why. It never says how. The compiler works out the full set of properties a capability must have from the rules and goals around it. It then judges every way of providing it on measured evidence, and it is indifferent to which of several equivalent answers ends up bound.

Canonical: https://endoskeletal.com/needs/
Last updated: 2026-09-28

This page follows one need through the whole process: an audit trail that Northwind's enterprise customers can search and trust.

## What a compiler is, and what this one does

Compilers exist so that people can say what they mean and leave the how to a program. Grace Hopper's A-0 in 1952 and FORTRAN in 1957 made the case: write formulas, not machine instructions, and let the compiler produce code nearly as good as a specialist would write by hand. Every compiler since has done some version of the same jobs:

1. **Parse.** Turn text into structure.
2. **Check.** Reject programs that can't mean anything, such as a type mismatch or an undefined name, before they ever run.
3. **Infer.** Work out what was left unsaid. Type inference derives the type of a value from how it is used, so the programmer doesn't have to write it.
4. **Optimize under the as-if rule.** Replace code with cheaper code only when the observable behavior is provably the same.
5. **Select and link for a target.** Choose among equivalent instruction sequences using a cost model of the specific machine, and resolve references to libraries.

Two later developments matter here. Just-in-time compilers, like the ones inside the JVM and JavaScript engines, watch a program run, compile the hot paths into fast specialized code guarded by assumptions, and fall back to the slower general path the moment an assumption breaks. And compiler writers learned not to trust data sheets: tools like llvm-exegesis measure instruction timings on the real chip, and projects like uops.info publish measurements because vendor manuals are incomplete.

**Diagram: A classic compiler and the Endoskeletal compiler, stage by stage.**

Two rows of seven stages. A classic compiler: source, parse, type check, infer types, optimize under the as-if rule, select instructions with a cost model of the CPU, link into a binary. The Endoskeletal compiler: the .esk specification, parse into records, felicity check, derive needs from norms and intents, search for equivalent realizations, rank them by the organization's own objective, and bind by a party with authority. Dotted lines pair each stage with its counterpart. A loop from bind back to derive shows that the Endoskeletal compiler reruns whenever evidence changes.

*Same five jobs, different machine. The highlighted stages are where the organization’s rules and measured evidence replace a CPU’s data sheet.*

Each job has a direct counterpart when the target is an organization:

| In a classic compiler | In the Endoskeletal compiler |
| --- | --- |
| Source program | The specification: seats, rules, goals and needs, in `.esk` |
| Target machine: a CPU | Everything available to run the organization: people, programs, models and providers, with their prices, terms and limits |
| Type checking | Felicity checking: every rule has a bearer, every power traces back to the constitution, every term is defined |
| Type inference | Need derivation: a capability's properties inferred from the rules and goals it serves |
| The as-if rule | Equivalence: any realization that satisfies every derived property on evidence is a correct implementation |
| Instruction selection with a cost model | Realization search, ranked by the organization's own objective: money, attention and exit risk |
| Measured instruction timings | Measured evidence: the organization's own probes and pooled observations, not provider claims |
| Linking | Binding, by a party that holds the power to bind, recorded in the log |
| JIT guards and deoptimization | Crystallization: reasoning distilled into a program with an envelope, handing back to reasoning when an input falls outside it ([how it works](https://endoskeletal.com/crystallize/)) |

Four things are different, and they're the reason this is a new language rather than a configuration format:

- **It never stops compiling.** A CPU's instruction set doesn't change after you ship. Prices, terms, outages and new offers change every week, so the compiler reruns whenever the evidence it depends on changes.
- **It proposes; it doesn't bind.** A CPU doesn't need to consent to being targeted. An organization does, so binding is an act that requires a power, performed by a party that holds it.
- **Unknown is not assumed true.** A C compiler trusts its target description. This one treats anything unproven as not permitted, and gives every unknown an owner.
- **It explains itself.** Every derived property, every rejected candidate and every ranking comes with its sources, in plain English.

## The need, as written

This is the entire declaration. It names the operations, one guarantee, and the goals and rules the capability serves. It has no properties, no vendor and no architecture.

*northwind.esk · scope Platform*

```esk
requires capability AuditTrail {
  operation append(e : au.Event) -> receipt : Entity
  operation query(t : cu.Tenant, q : Entity) -> rows : Entity
  guarantee eventually holds au.retained(e)
  for AuditExport, RecordsRetained, AuditIntegrity, TenantResidency, Frugal, ExitReadiness
}
```

The `for` line does the work. Each intent and norm named there is already in the specification for its own reasons: a contract clause, a SOC 2 control, a residency rule, a budget. The compiler reads what each of them implies for anything that stores audit events.

## What the compiler derives

**Diagram: Seven statements in the specification imply nine properties of the AuditTrail capability.**

Seven statements in the specification imply the nine properties of AuditTrail. Properties marked “often missed” are ones a design review commonly overlooks.

- **intent AuditExport** (enterprise contract § 6.1) → append p99 ≤ 150 ms, query p95 ≤ 2 s recent · ≤ 60 s older
- **claim ops.event-rate** (observed · 30-day peak × 1.5) → throughput ≥ 2,100 events/s
- **norm RecordsRetained** (SOC 2 CC7.2 · not defeasible) → retention ≥ 7 years, single-party delete impossible, even for our admins
- **norm AuditIntegrity** (enterprise contract § 6.3) → tamper evidence verifiable by the customer
- **norm TenantResidency** (DPA § 4 · constitutional) → jurisdiction tenant region, every copy and backup
- **fin.Budget PlatformShare** (weighted by intent Frugal) → cost ≤ 1,200 USD per month
- **norm ExitReadiness** (constitutional) → exit ≤ 5 days to another realization

*Nobody wrote these nine properties. Each follows from a rule or goal already in the specification, and changes when that rule or goal changes.*

*terminal*

```console
$ esk needs AuditTrail
AuditTrail · 0 properties written · 9 derived from 7 sources

  append-p99           <= 150 ms          ← intent AuditExport (contract § 6.1)
  query-p95            <= 2 s, ≤ 90 d     ← intent AuditExport (contract § 6.1)
                       <= 60 s, older
  throughput           >= 2,100 /s        ← claim ops.event-rate (peak × 1.5)
  retention            >= 7 y             ← norm RecordsRetained (soc2 CC7.2)
  single-party-delete  = impossible       ← norm RecordsRetained
                         not defeasible: must be prevented, not only forbidden
  tamper-evidence      = verifiable-by(t) ← norm AuditIntegrity (contract § 6.3)
  jurisdiction         = t.region         ← norm TenantResidency
                         every copy: replicas, backups, subprocessors
  cost                 <= 1,200 USD/mo    ← fin.Budget PlatformShare
  exit                 <= 5 d             ← norm ExitReadiness

$ esk realize AuditTrail --explain
searched 31 offers · 212 compositions · 8 survived decomposition
  equivalent   C  A  B  D     every condition T on evidence
  fails        E  jurisdiction F · cost F
               F  single-party-delete F · tamper-evidence F · query-older F
               G  single-party-delete F · tamper-evidence F
  unknown      H  terms N · retention N · single-party-delete N  → not admissible
proposal: C where cu.byob(t) holds, A otherwise; B, D as fallbacks
awaiting: bind by CTO (power PlatformBinds, collective)
```

Three of the nine are the ones design reviews miss:

- **Single-party delete must be impossible, including for your own admins.** RecordsRetained is a prohibition on *any* party and is not defeasible. A rule that can't be overridden can't rely on everyone behaving, so the compiler requires the realization to make the act impossible rather than merely forbidden. A lock that the account's root user can lift fails.
- **Jurisdiction applies to every copy.** TenantResidency is about custody, and custody includes replicas, backups and anyone with read access. A provider's support staff in another country count as a copy.
- **Tamper evidence must be checkable by the customer.** AuditIntegrity is owed to the tenant, so evidence only the operator can inspect doesn't discharge it.

The other six come from where you'd expect: latency and query speed from the contract, throughput from an observed claim (the 30-day peak with 50 % headroom, re-derived when the claim changes), retention from SOC 2, cost from the budget share, exit time from a constitutional norm.

## The rules it derived them from

*northwind.esk · the rules AuditTrail serves*

```esk
norm RecordsRetained {
  prohibition on any Party
  aim occurred execute(data.Delete, e) and e is au.Event and age(e) < 7 y
  source external soc2 # "CC7.2"
  level constitutional  not tradeable  not defeasible
}

norm AuditIntegrity {
  obligation of persona to cu.Tenant
  when e is au.Event and e.tenant = t
  aim holds au.verifiable-by(e, t)
  source external contract.Enterprise # "§ 6.3"
}

norm TenantResidency {
  prohibition on any Party
  aim exists x : Entity . x.tenant = t and custody-jurisdiction(x) != t.region
  level constitutional  not tradeable
}

-- what the compiler may believe about a provider's own description of itself
use ev.TrustPolicy(
  source-kind  = pv.Provider,
  base         = N,
  promote-when = ev.ConfirmedByOwnProbe(within = 14 d)
                 or ev.CorroboratedBy(3, independent = true),
  vocab        = { pv.latency, pv.throughput, pv.immutability, pv.residency }
) as ProviderSelfReports

position SecOfficer : org.Officer {
  cardinality 1
  incompatible with PlatformLead
}
```

## What providers say is not evidence

A provider is authoritative about its own price list, terms and deprecations. It is not authoritative about its own latency, durability, immutability or residency. Under the `ProviderSelfReports` policy above, those claims start at N (unknown). They become T only when the organization's own probe confirms them, or when three independent organizations report the same measurement through the pool.

**Candidates against the derived properties** (T true on evidence, F false, N unknown):

- **A. Locked object storage + queue + embedded query**: equivalent. jurisdiction of all copies: T (EU and US buckets, replication off; provider says “multi-region by default”: switched off and verified).
- **B. Search cluster we already run (90 d) + an email-journaling archive**: equivalent. query p95 ≤ 60 s (older): T (4.2 s; provider says “search in milliseconds”: measured 4.2 s, still inside 60 s).
- **C. Customer-held buckets, for tenants who opt in**: equivalent. every condition T.
- **D. Two providers, two roots, held by incompatible seats**: equivalent. every condition T.
- **E. Managed audit-log service**: fails. jurisdiction of all copies: F (US support subprocessor has read access; provider says “EU data residency”); cost ≤ 1,200 USD/mo: F (3,400 USD).
- **F. Our observability vendor's log archive (already under contract)**: fails. single-party delete impossible: F (an account admin can purge); customer-verifiable tamper evidence: F (nothing the customer can check); query p95 ≤ 60 s (older): F (rehydration takes 4–9 h; provider says “searchable archives”).
- **G. Append-only table in the primary Postgres**: fails. single-party delete impossible: F (a superuser can drop the table); customer-verifiable tamper evidence: F (nothing the customer can check).
- **H. A provider's new “immutable tables” (preview)**: unknown · not permitted. terms permit this use: N (preview terms exclude production data); throughput ≥ 2,100/s: N (no independent observation); retention ≥ 7 y: N (says 7 y; no evidence; provider says “WORM compliance”); single-party delete impossible: N (no evidence; provider says “cannot be deleted”); customer-verifiable tamper evidence: N (unknown); query p95 ≤ 60 s (older): N (unmeasured); exit ≤ 5 d: N (unknown).

Three things in this table only show up because claims were checked:

- **E** advertises EU data residency. Its subprocessor list gives a US support team read access, so jurisdiction is F.
- **F** advertises searchable archives. A probe measured 4 to 9 hours to rehydrate an archived month, so query speed for older records is F.
- **H** advertises WORM compliance for a preview feature. No one has measured it and the preview terms exclude production data, so it is N. N is not permission: H isn't rejected, it waits, owned, until evidence arrives.

Claims are also not always wrong in the provider's disfavor. B's archive says “search in milliseconds” and measured 4.2 seconds, which is well inside the 60 seconds required for records older than 90 days. The engine uses the measured number either way.

## Four equivalent designs

Each of these satisfies every derived property on evidence, so each is a correct implementation of the same specification. Only one of them resembles something you'd find in an architecture review.

1. **A. Locked object storage, a queue you already run, and a query engine with no servers.** An architect reaches for a database or a logging product. Nothing here is a logging product: the lock provides immutability, the batch layout provides query speed, the published root provides tamper evidence. Compliance-mode locks can't be lifted by the account's root user, which is exactly what the not-defeasible retention norm requires.
2. **B. The search cluster you already pay for, plus an email-journaling archive.** Email archives are sold to legal teams, not engineers. They already provide write-once retention, legal hold, per-custodian search and regional data centres; a tenant becomes a custodian. Admissible only because the archive's terms (§ 3.4) permit application-generated records. That clause is a recorded claim with a source; if it changes, this design drops out.
3. **C. Let the customer hold the records.** The instinct is to store more carefully. This moves custody to the party that wants the guarantee: Northwind can't delete what it never controls, and residency is the customer's own choice. Equivalent only for tenants who sign the bring-your-own-bucket addendum, which transfers the retention duty to them. The engine binds it per tenant: 38 of 120 today.
4. **D. Two cheap stores at two providers, and an org rule instead of a feature.** Neither store is immutable. The guarantee comes from the org chart: the two root credentials sit in positions declared incompatible, so no single party can hold both, and a deletion at one provider shows up in the published hash comparison. The compiler can prove single-party deletion is impossible because the incompatibility is a norm it checks on every appointment. Structure substitutes for a product feature.

## Choosing among equivalents

Equivalence is decided by the properties. The choice among equivalents is decided by the organization's own objective, which is itself in the specification: money, logged human attention, and exit risk, each priced.

- C · customer-held (38 tenants): 60 USD/month, 20 min/month of attention, exit 0 d, bound for opt-in tenants
- A · locked object storage: 410 USD/month, 30 min/month of attention, exit 2 d, bound for the other 82
- B · search + journaling archive: 430 USD/month, 60 min/month of attention, exit 4 d, warm fallback
- D · two providers, two roots: 380 USD/month, 150 min/month of attention, exit 1 d, second fallback

The engine proposes; it doesn't bind. The Architect's proposal is to bind C for every tenant where `cu.byob(t)` holds and A for everyone else, keeping B and D warm as fallbacks. The CTO holds the power to bind AuditTrail, and exercises it. The bind is one event in the log, with the whole argument attached: the derivation, the evidence behind every cell of the table, and the ranking.

## What stays fixed when the implementation changes

Suppose the object store in A raises its price by 60 %. A price change is a trusted lifecycle claim, so `Reconsider` fires. B now ranks first for tenants outside the opt-in group; the Architect proposes the swap, the CTO binds it, and the migration runs as ordinary commitments with deadlines. Across that change:

- not one line of the specification changes;
- every norm keeps holding, because B was admissible under all of them before it was chosen;
- the audit history is continuous, because the log records which realization held each event;
- every tenant's query interface stays the same, because it is the capability's operation, not a vendor API.

That is what separating needs from implementation buys you. Infrastructure code describes one implementation and has to be rewritten to change it. A specification describes the need, and any implementation that satisfies it on evidence is interchangeable with any other.

## How the search stays lawful

A composition search that finds an email archive can find many things. Three constraints keep it honest:

- **Terms are a property like any other.** Design B is admissible because a specific clause (§ 3.4) permits application-generated records. That clause is a recorded claim with a source. If the provider changes it, the claim changes and B drops out on the next evaluation.
- **Probes run inside the terms.** Every measurement comes from an experiment with its own budget and duration, and `WithinTerms` prohibits probing anything the contract doesn't permit. See [Capability discovery](https://endoskeletal.com/discovery/).
- **Unknown never counts as yes.** Any condition at N makes a candidate inadmissible, however attractive its price. Unknown conditions get an owner and a deadline, so candidates like H come back when evidence does.

More compositions of this kind, including a backup archive used as object storage and a relay plus a store used as a queue, are on [Non-obvious solutions](https://endoskeletal.com/solutions/). How measurements are shared across organizations is on [Learn once, apply everywhere](https://endoskeletal.com/learning/).
