diff --git a/.gitignore b/.gitignore index 3331ef0..37ee4b1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ bun.lockb bun.lock e2e/.dist/ *.ngw + +# Per-developer contract access map — canonical identities are committed, local paths are not +.project/contracts.local.yaml diff --git a/.project/VOCABULARY.md b/.project/VOCABULARY.md new file mode 100644 index 0000000..080bea6 --- /dev/null +++ b/.project/VOCABULARY.md @@ -0,0 +1,11 @@ + + +## Project vocabulary — canonical terms: use VERBATIM in any language, marked `like this` + +```text + «indexing» + `index` an ordinary public document that holds one entry per indexed object, plus its own field declaration (never: catalogue, registry, listing) + `deposit` a bare object reference left in an index document's inbox — open to anyone, and never an instruction (never: message, submission, request) + `curate` the owner resolving the references deposited on its index and adding what it can (never: process, ingest, sync) + `entry` what an index holds for one indexed object — its NURI and its value for the index's field (never: row, record, item) +``` diff --git a/.project/concepts/indexing/_overview.md b/.project/concepts/indexing/_overview.md new file mode 100644 index 0000000..a6a279c --- /dev/null +++ b/.project/concepts/indexing/_overview.md @@ -0,0 +1,42 @@ +--- +type: overview +summary: An index is an ordinary public document that only ever grows — what this repo publishes to applications, and what it consumes from NextGraph +triggers: + keywords: [index, indexing, curate, curation, deposit, depositor, entry, descriptor, reference, only-grows] + paths: + - "src/**" + - "test/**" + - "e2e/**" + - "README.md" +vocabulary: + - term: index + gloss: an ordinary public document that holds one entry per indexed object, plus its own field declaration + not: [catalogue, registry, listing] + - term: deposit + gloss: a bare object reference left in an index document's inbox — open to anyone, and never an instruction + not: [message, submission, request] + - term: curate + gloss: the owner resolving the references deposited on its index and adding what it can + not: [process, ingest, sync] + - term: entry + gloss: what an index holds for one indexed object — its NURI and its value for the index's field + not: [row, record, item] +--- + +# indexing — an index built on top of NextGraph, and the boundaries around it + +NextGraph has no indexing concept and will not grow one, so this is a construction **above** it, in its own repository, and the dependency runs one way only: this repo depends on `@ng-eventually/polyfill`, and the polyfill must never learn anything about indexing. + +An index is an **ordinary document** in its creator's public store. What makes it an index is that an application references its NURI in its own source. Anyone may hand it a reference by depositing into its inbox; its owner resolves those references itself and adds what it finds. **An index only ever grows** — no removal was ever built, and none is planned. + +## Roles at the repository boundary + +This repo is a **provider** of `indexing-layer`, which the Festipod application consumes, and a **consumer** of two engagements published by `ng-eventually-js`: `polyfill-surface` and `ng-e2e-helpers`. Each pair lives in its own interface folder: the engagement is pulled and never hand-edited, our declaration beside it is ours to keep current. + +Frictions are the main path for telling a provider what we need. They go in our `usage_` leaf, the signal goes out of band, and the entry is pruned once the engagement absorbs it. + +## Read first + +- `indexing-layer/contract_indexing-layer` — what an application may rely on from `@ng-helpers/indexing`. +- `polyfill-surface/usage_ng-helpers` — the exact polyfill entries this layer stands on, and what it had to build for want of them. +- `ng-e2e-helpers/usage_ng-helpers` — what our end-to-end suite calls, and the peer-dependency constraint it must respect. diff --git a/.project/concepts/indexing/indexing-layer/contract_indexing-layer.md b/.project/concepts/indexing/indexing-layer/contract_indexing-layer.md new file mode 100644 index 0000000..0f5ca23 --- /dev/null +++ b/.project/concepts/indexing/indexing-layer/contract_indexing-layer.md @@ -0,0 +1,146 @@ +--- +type: contract +summary: The API @ng-helpers/indexing exposes to an application — creating an index, depositing references into it, curating it, and reading it back +--- + +# contract_indexing-layer — `@ng-helpers/indexing` + +## Scope + +This package builds an **index** on top of NextGraph: an ordinary public document that holds one entry per indexed object, keyed by that object's NURI and carrying its value for a single declared field. + +It covers creating an index, handing an index a reference to an object (open to anyone), the owner resolving those references and adding what it can, and reading the entries back in order. + +It does not cover NextGraph itself — documents, identity, sharing, inboxes, transport — all of which reach it through a port you supply. It does not cover search, filtering, pagination, or querying by anything but the index's own field. It **never removes anything**, from anywhere, and that is a property of the engagement rather than a missing feature. + +### Deployment requirements + +An application using this package must: + +- have a NextGraph session already open under the identity it wants to act as, and build the port from it — `polyfillPort({ sessionId })`, where `sessionId` is what `@ng-eventually/polyfill`'s own `init(…)` hands its callback; +- reach a broker, since every operation here is a document read, a document write, or an inbox deposit; +- **hardcode the index's NURI in its own source.** Nothing marks a document as an index; the reference is what makes it one, and it is the only way anyone reaches it. + +One handle is one identity: the port carries a session and no call takes an identifier. Two users mean two handles. + +## Surface + +Full typed shape: the package's `types` entry, `@ng-helpers/indexing`. The load-bearing signatures: + +```ts +// ── wiring: one handle, one identity ───────────────────────────────────────── +export function polyfillPort(options: PolyfillPortOptions): NextGraphPort; +export interface PolyfillPortOptions { readonly sessionId: string | number } +export function indexing(port: NextGraphPort): Indexing; + +// ── addressing (re-exported so you import them from here) ──────────────────── +export type Nuri = `did:ng:${string}`; +export type NuriLike = Nuri | string; +export type { PrincipalId, UnionSubject, NextGraphPort, IncomingDeposit, ObjectResolution }; + +// ── everything this package does ───────────────────────────────────────────── +export interface Indexing { + /** Creates an index in THIS identity's public store and opens its inbox. Any user may. + * `field` is the predicate an indexed object must carry, declared once and for good; + * an empty or blank one throws. Returns the NURI to hardcode. */ + createIndex(field: string): Promise; + /** Deposits a bare reference into the index's inbox. Open to ANYONE. Nothing lands in + * the index until its owner curates. Throws if the index has no inbox. */ + refer(index: NuriLike, object: NuriLike): Promise; + /** OWNER only — resolves the references received and adds what it can. */ + curate(index: NuriLike): Promise; + /** The entries, ordered by value. Sugar over `readUnion([index])`. */ + read(index: NuriLike): Promise; +} + +// ── what an index holds ────────────────────────────────────────────────────── +export interface IndexEntry { readonly object: Nuri; readonly value: string } +export interface IndexDescriptor { readonly field: string } + +// ── what curating reports ──────────────────────────────────────────────────── +export type CurationOutcome = + | { readonly result: "indexed"; readonly object: Nuri; readonly value: string } + | { readonly result: "unchanged"; readonly object: Nuri } + | { readonly result: "skipped"; readonly object: Nuri; readonly reason: SkipReason } + | { readonly result: "unresolved"; readonly object: Nuri; readonly reason: string } + | { readonly result: "foreign"; readonly reason: string }; +export type SkipReason = "no-field" | "several-values" | "self-reference"; +export interface CurationReport { + readonly index: Nuri; + readonly outcomes: readonly CurationOutcome[]; // one per deposit, in deposit order +} + +// ── what travels from a depositor to a curator ─────────────────────────────── +export type IndexDeposit = Nuri; // the reference IS the whole payload +export function decodeReference(payload: unknown): Nuri | null; // untrusted input + +// ── the IRIs, for a reader going straight to `readUnion` ───────────────────── +export const INDEX_FIELD: string; // on the index's own subject: the field it indexes by +export const ENTRY_VALUE: string; // on an entry: that object's value for the field +``` + +## Guarantees + +**An index is an ordinary public document, and nothing marks it as one.** It lives in its creator's public store, so any reader opens it from the reference alone; its creator owns it, and any user may create one. + +**The field is declared once, inside the document, and cannot be changed.** `createIndex` refuses an empty or blank field at the door, because nothing here deletes and an index created on a useless field is useless for good. Declaring it in the document rather than in an application's source is what stops two applications curating the same index on two different fields. + +**`createIndex` opens the index's inbox itself.** Only the owner can, and creation is the one moment the owner is present, so it is not left to a later call to remember. + +**Depositing is open to anyone; writing is the owner's alone.** `refer` is a deposit into the index document's inbox — not a write — so a stranger can contribute to an index they do not own. `curate` reads that inbox and writes the document, and both are refused to anyone but the owner. The deposit is a **bare reference**: it carries no operation, no index reference (the inbox address already identifies the index), and no copy of the indexed value. What the object itself says is what goes in. + +**An index ONLY EVER GROWS.** There is no call that removes an entry, for anyone including the owner, and none is planned. This package cannot express a removal at all. The only answer to "this entry must go" is a fresh index. + +**Curation is convergent and order-independent.** Deposits are never consumed, so every run sees every deposit again; re-applying one re-resolves the reference and lands on the same result. An already-indexed object is skipped outright as `unchanged`. Nothing depends on the order references arrived in. + +**A reference that does not resolve costs nothing and is reported.** It comes back as `unresolved`, nothing is written for it, and nothing already in the index is touched — a later deposit adds it. Every unresolved reference appears in `CurationReport.outcomes`: harmless is not the same as invisible. + +**Reading is per-entry tolerant.** `read` returns entries ordered by value, ties broken on the object NURI, so two readers of the same index always see the same order. Values are compared **as strings** — an index whose field holds ISO-8601 dates therefore comes out in chronological order. A subject that is not a NURI is skipped, never thrown on, and only own properties are read: one stray triple cannot make every real entry unreadable. + +**An entry carrying several values keeps the smallest, deterministically** — which two curation runs racing each other can produce. The entry stays visible and every reader agrees on it. + +**`read` refuses a document that declares no field at all**, rather than answering "an empty index". An unreadable document and an empty one arrive as the same empty result, so an empty answer would be a failure wearing the shape of a fact. Retry before concluding the document is malformed. + +**An index declaring SEVERAL fields refuses to CURATE, loudly and permanently — and stays readable.** Picking one would leave a single list ordered by two different properties, because entries already written are never re-read. Existing entries stay visible and correct; nothing new is added. The refusal cannot be undone, and it says so instead of suggesting a retry. + +**Reading needs nothing from this package.** An application that knows the NURI can call the polyfill's `readUnion([index])` and get the entries as subjects — one per indexed object, keyed by its NURI — plus the index's own subject declaring its field, which `read` drops. `INDEX_FIELD` and `ENTRY_VALUE` are published for exactly that reader. + +**Every inbox payload is untrusted.** Anyone may deposit anything; `decodeReference` returns `null` for everything that is not a reference, and such a payload is reported as `foreign` rather than crashing curation. + +## Non-guarantees + +**No removal, at any level, ever.** Not an oversight and not "not yet": it was deliberately never built. Do not design around a future delete. + +**No refresh.** An already-indexed object is never re-read, so an object whose field value changes later keeps its original value in the index, indefinitely. + +**No private data.** Indexing is limited to objects the curator can open itself. An object the index's owner cannot read is simply `unresolved`. + +**`unresolved` does not tell you why.** Gone, unreadable, and "the read failed" arrive identically and are deliberately not distinguished. Never read it as "the object does not exist". + +**The narrow behaviours are open questions, not promises.** An object carrying nothing for the field is `skipped: "no-field"`; one carrying several values is `skipped: "several-values"`; a raced entry keeps the smallest value. Each is implemented in its narrowest form and reported rather than generalised, and each may change. + +**No stable error text.** What a throw or an `unresolved` reason reads is for a human reading a report. Do not parse it or branch on it. + +**No timing and no delivery promise.** A deposit is not in the index until the owner curates, and nothing here schedules curation. There is no notification, no queue depth, and no ordering between a deposit and a read. + +**The report grows with the inbox.** Since deposits are never retired, `CurationReport.outcomes` has one entry per deposit ever made, not per change. + +**No cross-broker reach.** A NURI resolves for users of the same broker. + +**No depositor authentication or rate limit.** Anyone may deposit any number of payloads into any index's inbox. + +## Change policy + +**Semver, and majors are the normal case.** This layer sits on a polyfill that is itself converging on a NextGraph that does not ship yet, and several of its own behaviours are declared above as open questions. Settling one of them narrows this surface — the major number will move often, and that frequency is the honest signal about this package, not an apology. Refusing to version would not slow the churn down; it would only take away the one tool you have for managing it. Pin a version, upgrade deliberately, and re-pull this contract each time. + +What each level means here, in this package's own terms: + +- **major** — an exported symbol is removed or renamed, **or** an existing call narrows: it now throws where it returned, or reports a state you did not have to handle before. Settling an open question counts, and so does adding a `CurationOutcome` variant or a `SkipReason` — an exhaustive `switch` in your code stops being exhaustive. A signature change a caller must react to counts; one that only accepts more than before does not. +- **minor** — a symbol is added and nothing existing moves: a new read helper, a new optional option. +- **patch** — a fix that changes neither the exported surface nor anything above under `## Guarantees`, including the text of a throw, which is explicitly disclaimed above. + +**A tag says where it comes from.** A release cut on `main` carries a **full version** (`1.0.0`), and the three rules above govern what changes between two full versions. Work still on a branch carries a **pre-release** of the version it is heading for (`1.0.0-dev.3`), which sorts *below* that version by construction — so you can pin what exists today while the tag itself tells you the surface has not been released and may still move before it is. Between two pre-releases of the same version nothing is promised: re-pull and read this leaf again. When the branch lands, the full version appears alongside; the pre-release keeps resolving, so no reference you pinned is ever withdrawn from under you. + +`1.0.0` is a baseline, not a claim of maturity: it is the number that makes your pin mean something. Nothing was released before it. This engagement is cut on `main`, so `1.0.0` is what you pin, and your `usage_` leaf anchors `against:` on that exact string — `against: @ng-helpers/indexing@1.0.0`. Had you pinned a pre-release, `against:` would carry that string, pre-release suffix included. + +There is no changelog file and no deprecation window: **the sections above are the release note.** A removal or a narrowing lands in `## Surface` and `## Guarantees` in the same version that ships it. Diff this leaf between two pulls — `## Guarantees` and `## Non-guarantees` before `## Surface`, because that is where a narrowing shows up first. diff --git a/.project/concepts/indexing/ng-e2e-helpers/usage_ng-helpers.md b/.project/concepts/indexing/ng-e2e-helpers/usage_ng-helpers.md new file mode 100644 index 0000000..7fcb6fa --- /dev/null +++ b/.project/concepts/indexing/ng-e2e-helpers/usage_ng-helpers.md @@ -0,0 +1,49 @@ +--- +type: usage +summary: What this repo's end-to-end suite calls from ng-e2e-helpers, the peer-dependency constraint it must respect, and the gaps it fills itself +against: ng-e2e-helpers@1.0.0-dev.1 +--- + +# usage_ng-helpers — `@ng-helpers/indexing`'s end-to-end suite on `ng-e2e-helpers` + +The consumer is this repository's end-to-end suite: one run that builds a small indexing application, serves it, gets real people into it through the real broker, and drives depositing and curating between them. + +Everything generic — the wallet lifecycle, the broker crossing, per-run profiles, bounds, the report shape, the recognition of the known browser failure modes — comes from the engagement and is **used, never reimplemented**. What is specific to this repository is the page that carries our application and the name its runs mint, and nothing else. + +## Consumed surface + +**Bounds** — `within`, `armSuiteDeadline`, `closeQuietly`, `firstLine`. + +**Measurement** — `measured`. + +**Browser and profiles** — `launchWatchedContext`, `closeContext`, `newPage`, and the type `RunProfile`. + +**Wallet** — `mintWalletProfile` and the type `WalletCredentials`. Each run mints its own. + +**Broker crossing** — `setupBrokerPage`. + +**Serving** — `serveOnEphemeralPort`, for the application bundle. + +**Known failure modes** — `browserTrouble`, wired as the suite's `diagnose`. + +**Report** — `declareSuite` and the type `Prerequisite`. + +**Constants** — `BROKER_ROUND_TRIP_MS`, `NEW_PAGE_MS`. + +Everything else the engagement offers is NOT consumed here: the screen inventory and its types, `completeBrokerLogin`, `emptyProfileContext`, `importWalletFile`, `exportWalletBytes`/`exportWalletFile`, `mintWalletBytes`, `mintWalletProfileKeepingContext`, `createWalletInContext`, `newRunProfile`, `isAlive`, `browserLost`, `lossDeclared`, `enclosingBound`, `frameTrouble`, the exported error classes, and the remaining `*_MS` constants. It is safely evolvable as far as this suite is concerned. + +## Constraints + +**The browser types are DERIVED from the helpers, never imported from `playwright` here.** The engagement declares Playwright a peer dependency, and this package reaches it as symlinked files — so TypeScript resolves the helpers' `playwright` from where those files really live. Importing the driver in this repository as well produced two structurally different copies of `BrowserContext`, and a context this suite had opened could not be handed back to the helper that opens contexts. Taking the types from the calls that return them leaves exactly one set, and a version skew can no longer express itself as a type error in code that is correct. + +**One wallet per run, minted, never carried.** The run's wallet name is stable and its identity is not; nothing survives a run, and no result depends on a previous one. + +**Nothing generic is reimplemented here.** Where a helper exists, it is called. That is a standing rule for this suite, not a preference — the crossing alone has cost days of misdiagnosis upstream, and a local copy of it would not carry those lessons. + +**Every wait is entered bounded.** No page or frame operation runs outside `within` or a helper that bounds it itself. + +## Frictions + +**Nothing bounds a call into the application iframe.** `frame.evaluate` carries no timeout of its own, so this suite wraps every bridge call itself. The engagement offers no way to obtain that bound, so each consumer re-derives the same wrapper — and the derivation is not free: the calls this suite reached for outside its own wrapper are exactly the ones that can still hang it. A bounded `evaluate` here would delete the wrapper and close the gap in one move. + +**"Measured and bounded" is one intent and two calls.** Sizing a bound from its own measurement is the discipline the engagement itself prescribes, yet every step in this suite has to compose `measured(what, ms, (bound) => within(what, bound, task))` by hand. Two consumers writing the same three-line helper is the tell that the pair belongs on the engagement. diff --git a/.project/concepts/indexing/polyfill-surface/usage_ng-helpers.md b/.project/concepts/indexing/polyfill-surface/usage_ng-helpers.md new file mode 100644 index 0000000..cad6d43 --- /dev/null +++ b/.project/concepts/indexing/polyfill-surface/usage_ng-helpers.md @@ -0,0 +1,51 @@ +--- +type: usage +summary: The seven polyfill entries the indexing layer stands on, the constraints it holds itself to, and what it had to build for want of a published helper +against: "@ng-eventually/polyfill@1.0.0-dev.1" +--- + +# usage_ng-helpers — `@ng-helpers/indexing` on `polyfill-surface` + +The consumer is the indexing layer: an index is an ordinary public document, contributions reach it through its inbox, and its owner curates it. NextGraph has no indexing concept, so nothing of what a deposit *means* belongs upstream — the polyfill's inbox stays generic and carries opaque payloads, and this layer decides what they say. + +The whole runtime dependency passes through **one file**, the adapter that builds our `NextGraphPort`. Everything else in the package is written against that port, so a change to the engagement breaks exactly one file and nothing else. The types are imported type-only, so they are literally the published ones rather than a copy that can drift. + +## Consumed surface + +**Placement** — `storeRegistry.createEntityDoc("public")`, for the index document itself; `storeRegistry.openDocumentInbox(doc)`, called once at creation. + +**Reading** — `readUnion([doc])` and the type `UnionSubject`. Used both for the index document and for resolving a deposited reference. + +**Writing** — `docs.sparqlUpdate(sessionId, update, anchor)`, with the document named ONCE as the anchor so the statement carries no `GRAPH <…>` wrapper. It is the only write this package makes, and it is always an `INSERT DATA` of literal triples. + +**Inbox** — `inbox.postToDocument(doc, { payload })` for depositing, `inbox.readForDocument(doc)` for the owner draining it, and the shape of `Deposit` (`from` / `payload` / `ts`), which our `IncomingDeposit` mirrors. + +**Types** — `Nuri`, `NuriLike`, `PrincipalId`, `UnionSubject`. + +**Bootstrap, in the end-to-end application only** — `configure`, this package's `init` (for the `sessionId` its callback delivers), and `ensureIdentity`. + +Everything else on the engagement is offered and NOT consumed: `watchShape`, `useShape`, `subscribeDoc`/`subscribeDocs`, `docs.docCreate`, `docs.sparqlQuery`, `storeRegistry.listMyEntityDocs`/`resolveScopeGraph`/`resolveWriteGraph`, `inbox.share`/`post`/`read`/`readSynced`/`readSyncedForDocument`/`processInbox`/`watch`, `ng`, `initNg`. It is safely evolvable as far as this layer is concerned. + +## Constraints + +**One port is one identity.** The polyfill's session is one user's and no call takes an identifier, so an `Indexing` handle is one person's. Two users mean two handles — which is also what keeps our multi-actor tests honest: a depositor obtains the index NURI the way an application does, never through a shared variable. + +**`sessionId` is relayed, never converted.** We carry it at the engagement's own `string | number` and hand it back untouched. + +**The write is add-only, and structurally so.** There is no delete builder anywhere in this package, and the only statement it can compose is an anchored `INSERT DATA`. Our own tests execute that SPARQL against an engine that refuses anything else, so a removal is unrunnable rather than merely undetected. This constrains what we ask of the engagement: we need exactly one write primitive and no more. + +**The inbox is opened at creation, from one place.** The engagement disclaims coalescing `openDocumentInbox` across pages, so we never open an index's inbox anywhere but in `createIndex`, under its owner, at the moment the document is created. + +**Every payload out of an inbox is untrusted input.** Anyone may deposit anything, so nothing read from a deposit reaches a query before being checked; a non-reference is reported, never thrown on. + +**An empty read is never treated as "empty".** Nothing in this layer reads `[]` from `readUnion` as "a valid index that happens to hold nothing" — the descriptor check refuses a document declaring no field, and that refusal aborts curation before a single write. + +## Frictions + +**The engagement does not say what `readUnion` does with a document it cannot read.** It says what it returns for a document it can, and it says that a rejection means "unknown, never absent" — but not whether an unreadable document inside the list comes back as a rejection or is swallowed into the result. We assume the worst (swallowed, therefore indistinguishable from empty) and code defensively around it. A sentence in `## Guarantees` settling this would replace a guess we are carrying in every read path. + +**No published NURI type guard.** `## Guarantees` states plainly that no type guard is published, so this layer carries its own — and it needs one, because a NURI arrives here from an untrusted inbox deposit and must be checked before it can be written between angle brackets. The check we wrote is a guess at what the engagement considers a valid `Nuri`, and a wrong guess is either a rejected legitimate reference or an injected one. + +**No published escaping helpers.** The engagement lists no `escapeIri`/`escapeLiteral`, and we compose SPARQL against `docs.sparqlUpdate` — so this package carries its own escaping rather than reach into the provider's internals. That is a security-relevant duplication of something the provider certainly already has: two implementations of the same rule, one of which is not the one the provider tests. + +**No published way to write triples above the raw SPARQL primitive.** `docs.sparqlUpdate` is the level we had to align on, which is why the two frictions above exist at all. A published "add these triples to this document" would remove the query composition, the escaping and the NURI validation from this layer in one move. diff --git a/.project/contracts.yaml b/.project/contracts.yaml new file mode 100644 index 0000000..3fbfe25 --- /dev/null +++ b/.project/contracts.yaml @@ -0,0 +1,35 @@ +# Inter-repo contracts. `publish:` is this project's engagement toward its consumers — +# listing a leaf here IS the act of publishing it; an unlisted `contract_` leaf is a draft. +# +# This project is BOTH a provider and a consumer, and the two faces are isolated at document +# level: `indexing-layer` never mentions what we consume, and our `usage_` leaves never +# mention what we promise. What backs a published guarantee is internal doctrine. +# +# PROVIDES indexing-layer (concepts/indexing/indexing-layer/) +# consumer: the Festipod application, in its own repo. It has not declared a +# usage leaf, and we do not author one on its behalf — an interface with no +# declared consumer simply runs in the one-document mode. +# +# CONSUMES polyfill-surface and ng-e2e-helpers, both published by `ng-eventually-js`. +# We pull each engagement and author the `usage_ng-helpers.md` beside it. +# +# `pullFrom:` values are CANONICAL remote identities, because this file travels with the +# branch. Per-developer local access lives in `.project/contracts.local.yaml`, which is +# gitignored and must never be committed. + +publish: + # paths are relative to `.project/` + indexing-layer: concepts/indexing/indexing-layer/contract_indexing-layer.md + +consume: + - contract: polyfill-surface + type: git + pullFrom: git@gitea.reconnexion.apps.gueraud.net:Reconnexion/ng-eventually.git/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md + ref: caps-p1a-and-virtual-user-boundary + into: concepts/indexing/polyfill-surface/ + + - contract: ng-e2e-helpers + type: git + pullFrom: git@gitea.reconnexion.apps.gueraud.net:Reconnexion/ng-eventually.git/.project/concepts/e2e-harness/ng-e2e-helpers/contract_ng-e2e-helpers.md + ref: caps-p1a-and-virtual-user-boundary + into: concepts/indexing/ng-e2e-helpers/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e7ea3d0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,3 @@ +## Project vocabulary (always loaded) + +@.project/VOCABULARY.md diff --git a/package.json b/package.json index 93c919b..832a3d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ng-helpers/indexing", - "version": "0.0.0", + "version": "1.0.0", "private": true, "type": "module", "description": "An indexing layer built on top of NextGraph, via @ng-eventually/polyfill. An index is an ordinary public document; contributions reach it through its inbox; its owner curates it.",