From c87cab373f1a56517f5e0a13c7f89c8f92fb2d5f Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Tue, 8 Sep 2026 23:36:57 -0400 Subject: [PATCH 1/4] docs(audit): specify contract foundation --- ...change-stream-audit-contract-foundation.md | 507 ++++++++++++++++++ 1 file changed, 507 insertions(+) create mode 100644 specs/2026-09-08-change-stream-audit-contract-foundation.md diff --git a/specs/2026-09-08-change-stream-audit-contract-foundation.md b/specs/2026-09-08-change-stream-audit-contract-foundation.md new file mode 100644 index 0000000..62371ff --- /dev/null +++ b/specs/2026-09-08-change-stream-audit-contract-foundation.md @@ -0,0 +1,507 @@ +# Change-stream audit contract foundation + +Status: accepted for initial implementation +Date: 2026-09-08 +Project: `performance` +Project root: `/Users/leonardo/Repositories/performance` + +## Problem + +Meteor needs a correctness audit that can eventually prove change-stream, +oplog, polling, publication, DDP, recovery, and cleanup behavior across a +bounded matrix. The first implementation attempted to deliver that entire +system at once. Its final form added 82 files and roughly 17,900 lines on top +of the benchmark platform, including an executable harness, owned MongoDB and +Meteor processes, a raw DDP client, fixture instrumentation, a declarative +interpreter, a large case catalog, validators, and tests. + +That implementation supplied useful design evidence, but it crossed too many +boundaries for an initial review. This change establishes only the durable +language needed to discuss the system: a specification and strict TypeScript +contracts. It deliberately does not claim that an audit can run. + +```text +Previous branch +=============== + + CLI + catalog + compiler + interpreter + process ownership + + MongoDB + Meteor + DDP + app probes + 90 cases + | + v + 82 files / ~17.9k LOC + + +This foundation +=============== + + specification + | + v + strict type contracts + | + v + compile-time examples only +``` + +## Evidence from the discarded implementation + +The discarded implementation demonstrated that the audit has four distinct +contract boundaries: + +1. authored intent: capabilities, applicability, cases, steps, and oracles; +2. compiled intent: one immutable plan for one exact coordinate; +3. observed evidence: ledgers produced independently by MongoDB, clients, + Meteor probes, and fault controllers; +4. reported outcome: pass, fail, incomplete, or not applicable, with identity + and cleanup attestations. + +It also showed that mixing those boundaries creates duplication. The case +catalog repeated applicability, fixtures, steps, evidence requirements, and +budgets. Runtime validators then repeated much of the TypeScript structure. +The initial contract should preserve the distinctions without preserving the +implementation. + +```text + what we ask for what happened + +----------------+ +----------------+ + | authored case | | evidence ledger| + +-------+--------+ +--------+-------+ + | | + v v + +-------+--------+ +--------+-------+ + | compiled plan |------------->| case evaluation| + +-------+--------+ future +--------+-------+ + | | + +---------------+----------------+ + v + +-------+-------+ + | audit result | + +---------------+ + + This change types every box and arrow. + It implements none of the arrows. +``` + +## Desired outcome + +Create a small, reviewable contract package that: + +- names the closed dimensions of an audit coordinate; +- models authored cases as discriminated unions; +- separates expected state from independently observed evidence; +- represents capability support and applicability without a case catalog; +- makes incomplete execution distinct from a failed correctness assertion; +- carries exact release, topology, harness, and plan identity; +- requires bounded execution budgets and explicit cleanup; +- can evolve without implying runtime validation or compatibility guarantees. + +## Scope + +### Included + +- Compile-time TypeScript contracts under `reliability/contracts/`. +- Branded identifiers and digests to prevent accidental cross-assignment. +- Closed unions for coordinates, steps, faults, evidence, oracles, and status. +- Versioned authored-case, compiled-plan, evidence-ledger, and result envelopes. +- Strict compiler configuration. +- Compile-time fixtures that exercise representative valid and invalid shapes. + +### Excluded + +- CLI commands or changes to `bench.js`. +- Runtime validators, parsers, normalization, or serialization. +- MongoDB, Meteor, proxy, replica-set, or cluster ownership. +- DDP clients and fixture-application instrumentation. +- Case catalogs, profiles, generated data, and negative-control catalogs. +- Runtime or integration tests. +- Dashboard and result-writer integration. +- Any claim that the future audit is executable. + +```text + IN THIS CHANGE LATER CHANGES + +-------------------------+ +-------------------------+ + | vocabulary | | parsing + validation | + | discriminated unions | | compiler | + | boundary envelopes | | runtime adapters | + | identity relationships | | owned environments | + | compile-time checks | | executable cases | + +-------------------------+ +-------------------------+ + | ^ + +--------- constrains -------------+ +``` + +## Assumptions + +- The audit remains experimental and has no stable public API consumers. +- TypeScript contracts are design-time guidance, not a trust boundary. +- Data entering from JSON, processes, sockets, databases, or Meteor must be + treated as `unknown` until a later runtime-validation layer is implemented. +- A case executes against exactly one transport, topology, observer order, + profile, seed, release identity, and harness revision. +- Evidence producers are independent enough that expected-model output cannot + masquerade as observed system evidence. +- Cleanup is part of correctness, not a best-effort epilogue. + +## Uncertainty + +The following choices remain intentionally open: + +- the serialization format and runtime schema library; +- the exact first set of executable cases; +- whether a compiler consumes authored objects, JSON, or generated definitions; +- how Meteor exposes authoritative observer and fallback evidence; +- how sharded-cluster and multi-instance environments are owned; +- which identities belong in benchmark results versus separate audit artifacts; +- whether the benchmark dashboard should ingest correctness results. + +These uncertainties do not prevent agreement on the boundary shapes. They do +prevent treating the shapes as a final compatibility promise. + +## Contract model + +### Layering + +```text + +---------------------------------------------------------------+ + | reliability/contracts/index.ts | + | public type-only export surface | + +-------------------------------+-------------------------------+ + | + +----------------+----------------+ + | | + v v + +------------------------------+ +------------------------------+ + | primitives.ts | | audit.ts | + | branded IDs |<-| coordinates | + | JSON/EJSON values | | capabilities | + | exact identity types | | evidence + results | + +---------------+--------------+ +---------------+--------------+ + ^ ^ + | | + +----------------+----------------+ + | + v + +------------------------------+ + | declarative.ts | + | authored definitions | + | steps + oracles | + | compiled plans | + +------------------------------+ +``` + +All exports are types. Importing the package must emit no JavaScript and cause +no side effects. + +### Identity + +Identity fields answer different questions and must not be interchangeable. + +```text + AuditId -------- identifies one whole audit invocation + | + +-- RunId --- scopes fixtures, evidence, and cleanup + | + +-- CaseId -- identifies authored behavior + | + +-- coordinate + profile + seed + | + v + PlanDigest + + ReleaseIdentity ---- exact Meteor source and package set + HarnessIdentity ---- exact contract and harness revisions + EnvironmentIdentity - exact MongoDB topology and members +``` + +Identifiers and SHA-256 digests use distinct branded string types. Branding is +compile-time friction only; construction and validation belong to a later +runtime boundary. + +### Coordinate + +```text + +----------------+ + | CaseCoordinate | + +-------+--------+ + | + +-----------+----------+----------+-----------+ + | | | | + v v v v + transport topology observerOrder seed + sockjs replica_set [changeStreams, uint32 + sockjs-polling standalone oplog, ...] + uws sharded_cluster +``` + +The type system closes the vocabulary but cannot enforce numeric bounds, +non-empty arrays, uniqueness, or a valid observer fallback order. Those are +documented invariants for future runtime validation. + +### Authored case + +```text + CaseDefinitionV1 + | + +-- metadata -------- title, rationale, source + +-- applicability --- allowed coordinates + +-- parameters ------ typed defaults and bounds + +-- fixture --------- collection, publication, generator, sizing refs + +-- preconditions --- availability required before execution + +-- steps ----------- closed discriminated union + +-- evidence -------- independent producers and ledger names + +-- oracles --------- expected ref <-> observed ref + +-- diagnostics ----- explicitly non-gating measurements + +-- cleanup --------- run-scoped and verified + +-- budget ---------- hard resource and time ceilings + +-- sharing --------- isolated +``` + +Steps are declarative instructions, not callbacks or shell fragments: + +```text + subscribe mongo_write wait barrier + | | | | + +----------------+--------------+---------------+ + | + v + DeclarativeStep + ^ + +----------------+--------------+---------------+ + | | | | + client_lifecycle fault snapshot seal_evidence +``` + +Every step carries an ID, a timeout policy, and a failure disposition. The +closed union prevents an implementation from silently accepting arbitrary +executable code. + +### Evidence independence + +Expected state and observed state must have different provenance. + +```text + mutation description ---> expected-model ledger ----+ + | + v + +--------+ + MongoDB query ---------> mongodb ledger -------->| oracle | + DDP observation -------> ddp-client ledger ----->| compare| + Meteor internals ------> meteor-probe ledger --->| | + fault lifecycle -------> fault ledger ---------->| | + +---+----+ + | + +-----------+-----------+ + | | + v v + hard gate diagnostic +``` + +An oracle names one expected reference and one observed reference. A hard gate +can fail the case; a diagnostic can explain behavior but cannot turn failure +into success. + +### Outcome semantics + +```text + case started + | + +--------------+--------------+ + | | + v v + precondition false execution attempted + | | + v +--------+--------+ + not_applicable | | + v v + evidence complete evidence incomplete + | | + +------+-----+ v + | | incomplete + v v + passed failed +``` + +- `passed`: all required hard oracles pass and cleanup is attested. +- `failed`: required evidence exists and at least one hard oracle fails. +- `incomplete`: required evidence or cleanup attestation is missing. +- `not_applicable`: declared preconditions exclude the coordinate before the + behavioral assertion is attempted. + +Infrastructure failure must never become a correctness pass. + +### Version flow + +```text + CaseDefinitionV1 + | + | future compiler + v + CompiledCasePlanV1 + | + | future interpreter + v + EvidenceLedgerV1 -----> AuditCaseResultV1 + | + v + AuditRunResultV1 +``` + +Each envelope has a literal `schemaVersion`. A future incompatible shape gets +a new named type; it does not widen the existing version with optional fields. + +## Type-level invariants + +The initial implementation must encode these invariants: + +- discriminants select the valid fields for every value reference, parameter, + precondition, step, observer expectation, and outcome; +- expected-model evidence is distinct from system-observed evidence; +- a plan contains resolved values, not unresolved authored parameters; +- case and run results carry exact coordinate and identity objects; +- cleanup has an explicit result and evidence reference; +- failures and incomplete outcomes carry non-empty reason tuples; +- successful outcomes cannot carry failure reasons; +- all collections and nested records are readonly; +- extension data is absent from core contracts rather than admitted through + broad string index signatures. + +The following require future runtime validation: + +- identifier syntax and digest length; +- numeric ranges and finite values; +- uniqueness and referential integrity; +- maximum collection sizes and nesting depth; +- step ordering and barrier closure; +- plan and ledger digest correctness; +- release, topology, observer, and cleanup attestation truth. + +```text + TypeScript can prove Runtime must prove + -------------------- ------------------ + known discriminant input is trustworthy + required field exists string matches syntax + union branch is coherent arrays are bounded/unique + result state is coherent references point backward + readonly consumer view digest matches bytes + evidence reflects reality +``` + +## Risks and mitigations + +### False confidence + +Risk: reviewers mistake exhaustive-looking types for input validation. + +Mitigation: the spec and module comments state that untrusted data remains +`unknown`; no parser or `as`-based constructor is exported. + +### Premature compatibility + +Risk: downstream code adopts an experimental contract as stable. + +Mitigation: version envelopes explicitly, keep the package internal, and state +that the first runtime implementation may revise V1 before release. + +### Excessive vocabulary + +Risk: the types preserve the previous branch's breadth and become a catalog in +disguise. + +Mitigation: retain behavior categories and boundary shapes, but omit individual +case IDs, generator IDs, profile data, and capability records. + +### Invalid states still representable + +Risk: TypeScript cannot express every bounded or relational constraint. + +Mitigation: document runtime-only invariants beside the relevant types and make +future parsing an explicit implementation phase. + +### Contract drift + +Risk: examples and prose diverge from exported types. + +Mitigation: compile representative examples with the strict contract +configuration and use `satisfies` so excess or missing fields are detected. + +## Recovery and rollback + +The pre-reduction branch tip is preserved locally as: + +```text +backup/feat-change-stream-audit-pre-reduction-20260908 +``` + +Rollback options: + +```text +Need one discarded detail? git show backup/...: +Need selected commits? git cherry-pick +Need the entire old branch? reset the feature ref to backup/... +Need only the clean foundation? keep this branch as-is +``` + +No persisted data, production dependency, or runtime behavior is changed by +this foundation. + +## Direct rollout + +This is a design foundation, so rollout consists only of merging the spec and +type contracts. No feature flag, data migration, deployment, or operational +coordination is required. + +```text + review spec --> review contracts --> run typecheck --> merge + | | | + v v v + intent sound? shapes match intent? compiler clean? +``` + +## Executable checklist + +- [x] Select `upstream/main` as the clean feature base. +- [x] Preserve the discarded feature tip in a local backup ref. +- [ ] Add branded primitives and exact identity contracts. +- [ ] Add coordinate, capability, evidence, cleanup, and result contracts. +- [ ] Add authored-case and compiled-plan contracts. +- [ ] Add a type-only public export surface. +- [ ] Add strict TypeScript configuration. +- [ ] Add compile-time positive and negative examples. +- [ ] Run the contract typecheck. +- [ ] Run the existing JavaScript unit suite. +- [ ] Confirm the final diff contains no runtime audit implementation. +- [ ] Make `origin/main` exactly match `upstream/main`. +- [ ] Force-push the reduced feature branch. +- [ ] Verify both remote ref hashes. + +## Acceptance criteria + +1. The branch is based directly on the fetched `upstream/main` commit. +2. The diff contains only this specification, type-only contracts, compile-time + fixtures, and the minimum package/configuration changes needed to typecheck. +3. Importing the contract surface emits no JavaScript and has no side effects. +4. Strict TypeScript compilation accepts representative valid contracts and + rejects representative invalid discriminated-union branches. +5. Existing JavaScript tests continue to pass. +6. No executable audit command, runtime validator, environment owner, network + client, application probe, case catalog, or generated definition remains. +7. `origin/main` resolves to the same commit as `upstream/main`. +8. `origin/feat/change-stream-audit` resolves to the reduced branch tip. + +## Review order + +```text + 1. Scope and exclusions + | + v + 2. Boundary and evidence diagrams + | + v + 3. Type-level versus runtime invariants + | + v + 4. Exported TypeScript contracts + | + v + 5. Compile-time fixtures and configuration +``` From 2e88badcfe8574543335655d9fc1494a03fb2387 Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Tue, 8 Sep 2026 23:44:05 -0400 Subject: [PATCH 2/4] feat(audit): add type contract foundation --- package-lock.json | 3 +- package.json | 6 +- reliability/contracts/audit.ts | 278 ++++++++++++++ reliability/contracts/declarative.ts | 341 ++++++++++++++++++ reliability/contracts/index.ts | 3 + reliability/contracts/primitives.ts | 38 ++ ...change-stream-audit-contract-foundation.md | 33 +- tests/types/change-stream-audit-contracts.ts | 153 ++++++++ tsconfig.audit-contracts.json | 23 ++ 9 files changed, 860 insertions(+), 18 deletions(-) create mode 100644 reliability/contracts/audit.ts create mode 100644 reliability/contracts/declarative.ts create mode 100644 reliability/contracts/index.ts create mode 100644 reliability/contracts/primitives.ts create mode 100644 tests/types/change-stream-audit-contracts.ts create mode 100644 tsconfig.audit-contracts.json diff --git a/package-lock.json b/package-lock.json index e7918a8..ee80db3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "@types/node": "^20.14.11", "artillery": "^2.0.17", "m": "^1.9.0", - "pidusage": "^3.0.2" + "pidusage": "^3.0.2", + "typescript": "5.4.5" }, "engines": { "node": ">=24", diff --git a/package.json b/package.json index f882a27..cd41783 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "type": "module", "main": "bench.js", "scripts": { - "test": "node --test 'tests/unit/**/*.test.js'" + "test": "node --test 'tests/unit/**/*.test.js'", + "typecheck:audit-contracts": "tsc --project tsconfig.audit-contracts.json" }, "author": "", "license": "ISC", @@ -18,7 +19,8 @@ "@types/node": "^20.14.11", "artillery": "^2.0.17", "m": "^1.9.0", - "pidusage": "^3.0.2" + "pidusage": "^3.0.2", + "typescript": "5.4.5" }, "volta": { "node": "24.0.0" diff --git a/reliability/contracts/audit.ts b/reliability/contracts/audit.ts new file mode 100644 index 0000000..97b88ac --- /dev/null +++ b/reliability/contracts/audit.ts @@ -0,0 +1,278 @@ +import type { + AuditId, + ByteCount, + CapabilityId, + CaseId, + ContractId, + EjsonValue, + EvidenceEntryId, + FaultId, + HarnessRevision, + LedgerId, + Milliseconds, + NonEmptyReadonlyArray, + OracleId, + ProfileId, + RunId, + Sha256Digest, + StepId, + UInt32, +} from './primitives.js'; + +export type ObserverDriver = 'changeStreams' | 'oplog' | 'polling'; +export type DdpTransport = 'sockjs' | 'sockjs-polling' | 'uws'; +export type MongoTopology = 'replica_set' | 'sharded_cluster' | 'standalone'; + +export interface CaseCoordinate { + readonly caseId: CaseId; + readonly transport: DdpTransport; + readonly topology: MongoTopology; + /** Ordered, non-empty, and unique after runtime validation. */ + readonly observerOrder: NonEmptyReadonlyArray; + /** An unsigned 32-bit integer after runtime validation. */ + readonly seed: UInt32; + readonly faultId?: FaultId; +} + +export interface ApplicabilityScope { + readonly topologies: NonEmptyReadonlyArray; + readonly transports: NonEmptyReadonlyArray; + readonly observerOrders: NonEmptyReadonlyArray< + NonEmptyReadonlyArray + >; +} + +export type CapabilityExpectation = + | 'supported' + | 'fallback_required' + | 'not_supported' + | 'out_of_scope'; + +interface CapabilityDefinitionBase { + readonly id: CapabilityId; + readonly source: string; + readonly rationale: string; +} + +export type CapabilityDefinition = + | (CapabilityDefinitionBase & { + readonly expectation: 'supported' | 'fallback_required'; + readonly requiredCases: NonEmptyReadonlyArray; + readonly applicability: NonEmptyReadonlyArray; + }) + | (CapabilityDefinitionBase & { + readonly expectation: 'not_supported' | 'out_of_scope'; + readonly requiredCases: readonly []; + readonly applicability: readonly ApplicabilityScope[]; + }); + +export type MeteorSourceIdentity = + | Readonly<{ + mode: 'release'; + requestedRelease: string; + actualRelease: string; + sourceRevision: `release:${string}`; + fixtureRelease: `METEOR@${string}`; + }> + | Readonly<{ + mode: 'checkout'; + requestedCheckout: string; + sourceRevision: Sha256Digest; + fixtureRelease: `METEOR@${string}`; + }> + | Readonly<{ + mode: 'system'; + executable: string; + actualRelease: string; + sourceRevision: Sha256Digest; + fixtureRelease: `METEOR@${string}`; + }>; + +export interface ReleaseIdentity { + readonly source: MeteorSourceIdentity; + readonly packageVersionsDigest: Sha256Digest; + readonly settingsDigest: Sha256Digest; +} + +export interface HarnessIdentity { + readonly revision: HarnessRevision; + readonly dirty: boolean; + readonly contractId: ContractId; + readonly contractDigest: Sha256Digest; + readonly executionEnvironment: string; +} + +export interface MongoMemberIdentity { + readonly name: string; + readonly role: 'primary' | 'secondary' | 'mongos' | 'config' | 'standalone'; +} + +interface MongoEnvironmentIdentityBase { + readonly serverVersion: string; + readonly featureCompatibilityVersion: string; + readonly members: readonly MongoMemberIdentity[]; +} + +export type MongoEnvironmentIdentity = + | (MongoEnvironmentIdentityBase & { + readonly topology: 'replica_set'; + readonly replicaSetName: string; + }) + | (MongoEnvironmentIdentityBase & { + readonly topology: 'sharded_cluster'; + readonly clusterName: string; + }) + | (MongoEnvironmentIdentityBase & { + readonly topology: 'standalone'; + }); + +export interface AuditIdentity { + readonly auditId: AuditId; + readonly runId: RunId; + readonly release: ReleaseIdentity; + readonly harness: HarnessIdentity; + readonly mongo: MongoEnvironmentIdentity; +} + +export type ExpectedEvidenceProducer = 'expected_model'; + +export type ObservedEvidenceProducer = + | 'mongodb' + | 'ddp_client' + | 'meteor_probe' + | 'fault_controller'; + +export type EvidenceProducer = + | ExpectedEvidenceProducer + | ObservedEvidenceProducer; + +export interface EvidenceReference< + Producer extends EvidenceProducer = EvidenceProducer, +> { + readonly producer: Producer; + readonly ledgerId: LedgerId; + readonly entryId: EvidenceEntryId; + readonly stepId: StepId | 'cleanup'; +} + +export interface EvidenceEntry< + Producer extends EvidenceProducer = EvidenceProducer, +> { + readonly id: EvidenceEntryId; + readonly producer: Producer; + readonly sequence: UInt32; + readonly capturedAt: string; + readonly kind: string; + readonly payload: EjsonValue; + readonly digest: Sha256Digest; +} + +export interface EvidenceLedger< + Producer extends EvidenceProducer = EvidenceProducer, +> { + readonly schemaVersion: 1; + readonly id: LedgerId; + readonly auditId: AuditId; + readonly runId: RunId; + readonly producer: Producer; + readonly entries: readonly EvidenceEntry[]; + readonly digest: Sha256Digest; + readonly sealed: boolean; +} + +export type OracleFamily = + | 'snapshot_exact' + | 'event_present' + | 'event_absent' + | 'revision_monotonic' + | 'field_absent' + | 'observer_identity' + | 'fallback_identity' + | 'transport_identity' + | 'session_identity' + | 'fault_witness' + | 'cleanup_complete' + | 'release_identity' + | 'required_coordinate'; + +export interface OracleEvaluation { + readonly oracleId: OracleId; + readonly family: OracleFamily; + readonly gate: 'hard' | 'diagnostic'; + readonly status: 'passed' | 'failed' | 'unavailable'; + readonly expected: EvidenceReference; + readonly observed: EvidenceReference; + readonly reason?: string; +} + +export type CleanupResult = + | Readonly<{ + status: 'complete'; + verifiedEmpty: true; + evidence: EvidenceReference; + }> + | Readonly<{ + status: 'failed'; + verifiedEmpty: false; + evidence?: EvidenceReference; + reasons: NonEmptyReadonlyArray; + }>; + +export interface AuditMeasurements { + readonly wallClockMs: Milliseconds; + readonly values: Readonly>; +} + +export type AuditCaseOutcome = + | Readonly<{ + status: 'passed'; + reasons: readonly []; + }> + | Readonly<{ + status: 'failed'; + reasons: NonEmptyReadonlyArray; + }> + | Readonly<{ + status: 'incomplete'; + reasons: NonEmptyReadonlyArray; + }> + | Readonly<{ + status: 'not_applicable'; + reasons: NonEmptyReadonlyArray; + }>; + +export interface AuditCaseResultV1 { + readonly schemaVersion: 1; + readonly identity: AuditIdentity; + readonly coordinate: CaseCoordinate; + readonly profileId: ProfileId; + readonly caseDefinitionDigest: Sha256Digest; + readonly compiledPlanDigest: Sha256Digest; + readonly interpreterVersion: string; + readonly stepLedgerDigest: Sha256Digest; + readonly evidenceLedgerDigests: Readonly< + Partial> + >; + readonly oracles: readonly OracleEvaluation[]; + readonly cleanup: CleanupResult; + readonly measurements: AuditMeasurements; + readonly outcome: AuditCaseOutcome; +} + +export type AuditRunOutcome = + | Readonly<{ status: 'passed'; reasons: readonly [] }> + | Readonly<{ + status: 'failed' | 'incomplete'; + reasons: NonEmptyReadonlyArray; + }>; + +export interface AuditRunResultV1 { + readonly schemaVersion: 1; + readonly identity: AuditIdentity; + readonly startedAt: string; + readonly completedAt: string; + readonly cases: readonly AuditCaseResultV1[]; + readonly outcome: AuditRunOutcome; + readonly artifactDigest: Sha256Digest; + readonly artifactBytes: ByteCount; +} diff --git a/reliability/contracts/declarative.ts b/reliability/contracts/declarative.ts new file mode 100644 index 0000000..b5afbfa --- /dev/null +++ b/reliability/contracts/declarative.ts @@ -0,0 +1,341 @@ +import type { + ApplicabilityScope, + DdpTransport, + EvidenceProducer, + MongoTopology, + ObservedEvidenceProducer, + ObserverDriver, + OracleFamily, +} from './audit.js'; +import type { + ByteCount, + CaseId, + ContractId, + EjsonValue, + FaultId, + LedgerId, + Milliseconds, + NonEmptyReadonlyArray, + OracleId, + ParameterName, + PositiveInteger, + ProfileId, + RunId, + Sha256Digest, + StepId, + UInt32, +} from './primitives.js'; + +export type DeclarativeValueReference = + | Readonly<{ kind: 'literal'; value: EjsonValue }> + | Readonly<{ kind: 'parameter'; name: ParameterName }> + | Readonly<{ + kind: 'coordinate'; + field: 'seed' | 'transport' | 'topology' | 'observerOrder'; + }> + | Readonly<{ kind: 'run'; field: 'runId' }> + | Readonly<{ kind: 'fixture'; field: 'documents' | 'subscriberIds' }> + | Readonly<{ kind: 'step'; stepId: StepId; output: string }>; + +export type ParameterDefinition = + | Readonly<{ + type: 'integer'; + default: number; + minimum: number; + maximum: number; + }> + | Readonly<{ + type: 'enum'; + default: string; + values: NonEmptyReadonlyArray; + }> + | Readonly<{ type: 'boolean'; default: boolean }>; + +export type DeclarativeSelector = + | Readonly<{ kind: 'fixture_document'; index: UInt32 }> + | Readonly<{ + kind: 'field_equals'; + field: string; + value: DeclarativeValueReference; + }>; + +export interface DeclarativeSortField { + readonly field: string; + readonly direction: 'ascending' | 'descending'; +} + +export type DeclarativeQuery = + | Readonly<{ kind: 'unordered' }> + | Readonly<{ + kind: 'ordered'; + sort: NonEmptyReadonlyArray; + }> + | Readonly<{ + kind: 'windowed'; + sort: NonEmptyReadonlyArray; + skip: DeclarativeValueReference; + limit: DeclarativeValueReference; + }> + | Readonly<{ + kind: 'selector'; + selector: DeclarativeSelector; + }> + | Readonly<{ + kind: 'projection'; + fields: NonEmptyReadonlyArray; + }> + | Readonly<{ + kind: 'multiple_projections'; + projections: NonEmptyReadonlyArray>; + }> + | Readonly<{ + kind: 'unsupported_selector'; + operator: 'json_schema'; + }> + | Readonly<{ kind: 'change_stream_unavailable' }>; + +export type DeclarativeMutation = + | Readonly<{ + kind: 'set' | 'push'; + path: NonEmptyReadonlyArray; + value: DeclarativeValueReference; + }> + | Readonly<{ + kind: 'unset'; + path: NonEmptyReadonlyArray; + }> + | Readonly<{ + kind: 'increment'; + path: NonEmptyReadonlyArray; + amount: DeclarativeValueReference; + }> + | Readonly<{ kind: 'fixture_document'; index: UInt32 }> + | Readonly<{ kind: 'generated_document'; generator: string }> + | Readonly<{ kind: 'projection_variant'; variant: string }> + | Readonly<{ kind: 'none' }>; + +export type DeclarativeTransition = + | Readonly<{ kind: 'insert' | 'replace' | 'delete' }> + | Readonly<{ + kind: 'set_field' | 'append_array'; + path: NonEmptyReadonlyArray; + value: DeclarativeValueReference; + }> + | Readonly<{ + kind: 'remove_field'; + path: NonEmptyReadonlyArray; + }> + | Readonly<{ + kind: 'increment_field'; + path: NonEmptyReadonlyArray; + amount: DeclarativeValueReference; + }> + | Readonly<{ kind: 'projection_variant'; variant: string }>; + +interface DeclarativeStepBase { + readonly id: StepId; + readonly timeoutMs?: Milliseconds; + readonly onFailure: 'fail_case' | 'incomplete_case'; + readonly concurrencyGroup?: string; +} + +export type DeclarativeStep = + | (DeclarativeStepBase & + Readonly<{ + kind: 'subscribe'; + query: DeclarativeQuery; + clients: DeclarativeValueReference; + }>) + | (DeclarativeStepBase & + Readonly<{ + kind: 'mongo_write'; + operation: + | 'insert_one' + | 'insert_many' + | 'update_one' + | 'replace_one' + | 'delete_one' + | 'delete_many'; + selector: DeclarativeSelector; + mutation: DeclarativeMutation; + expectedTransition: DeclarativeTransition; + }>) + | (DeclarativeStepBase & + Readonly<{ + kind: 'wait'; + predicate: string; + inputs: Readonly>; + }>) + | (DeclarativeStepBase & + Readonly<{ + kind: 'barrier'; + barrier: string; + schedule: 'serialized' | 'concurrent' | 'burst'; + participants: DeclarativeValueReference; + }>) + | (DeclarativeStepBase & + Readonly<{ + kind: 'client_lifecycle'; + action: + | 'connect' + | 'disconnect' + | 'reconnect' + | 'resume' + | 'stop_subscription' + | 'shutdown'; + clients: DeclarativeValueReference; + }>) + | (DeclarativeStepBase & + Readonly<{ + kind: 'fault'; + operation: 'activate' | 'restore'; + controller: FaultController; + faultId: FaultId; + }>) + | (DeclarativeStepBase & + Readonly<{ + kind: 'snapshot'; + producer: EvidenceProducer; + scope: 'expected' | 'mongodb' | 'ddp' | 'all'; + }>) + | (DeclarativeStepBase & Readonly<{ kind: 'seal_evidence' }>); + +export type FaultController = + | 'catchup_timeout' + | 'change_stream_error' + | 'change_stream_close' + | 'ddp_client_disconnect' + | 'meteor_mongo_interruption' + | 'mongodb_primary_step_down' + | 'replica_set_election' + | 'snapshot_pause' + | 'stream_restart' + | 'watch_setup_pause'; + +export type CasePrecondition = + | Readonly<{ + kind: 'actual_observer_available'; + driver: ObserverDriver; + }> + | Readonly<{ + kind: 'observer_driver_unavailable'; + driver: ObserverDriver; + }> + | Readonly<{ + kind: 'topology_available' | 'topology_matches_coordinate'; + topology: MongoTopology; + }> + | Readonly<{ + kind: 'transport_available'; + transport: DdpTransport; + }> + | Readonly<{ + kind: 'fault_controller_available'; + controller: FaultController; + }>; + +export type ObserverEvidenceRequirement = + | Readonly<{ + kind: 'selected'; + driver: DeclarativeValueReference; + }> + | Readonly<{ + kind: 'fallback'; + from: ObserverDriver; + to: ObserverDriver; + reasonRequired: true; + }>; + +export interface CaseEvidenceRequirements { + readonly requiredProducers: NonEmptyReadonlyArray; + readonly observer: ObserverEvidenceRequirement; + readonly transportIdentity: 'required' | 'diagnostic'; + readonly fault: Readonly<{ + kind: 'activated_and_restored'; + controller: FaultController; + }> | null; + readonly ledgers: NonEmptyReadonlyArray; +} + +export interface DeclarativeOracle { + readonly id: OracleId; + readonly family: OracleFamily; + readonly producer: ObservedEvidenceProducer; + readonly expected: DeclarativeValueReference; + readonly observed: Readonly<{ + producer: ObservedEvidenceProducer; + stepId: StepId | 'cleanup'; + ledgerId: LedgerId; + }>; + readonly failureReason: string; + readonly gate: 'hard' | 'diagnostic'; +} + +export interface ExecutionBudget { + readonly maximumSteps: PositiveInteger; + readonly maximumDocuments: PositiveInteger; + readonly maximumSubscribers: PositiveInteger; + readonly maximumPayloadBytes: ByteCount; + readonly maximumEvidenceEntries: PositiveInteger; + readonly stepTimeoutMs: Milliseconds; + readonly caseTimeoutMs: Milliseconds; + readonly maximumRetries: 0 | 1; +} + +export interface CaseDefinitionV1 { + readonly schemaVersion: 1; + readonly id: CaseId; + readonly title: string; + readonly source: string; + readonly rationale: string; + readonly applicability: NonEmptyReadonlyArray; + readonly parameters: Readonly>; + readonly fixture: Readonly<{ + collection: 'reliabilityDocuments'; + publication: 'reliability.documents'; + generator: string; + subscribers: DeclarativeValueReference; + documents: DeclarativeValueReference; + payloadBytes: DeclarativeValueReference; + }>; + readonly preconditions: readonly CasePrecondition[]; + readonly steps: NonEmptyReadonlyArray; + readonly evidence: CaseEvidenceRequirements; + readonly oracles: NonEmptyReadonlyArray; + readonly diagnostics: readonly Readonly<{ + kind: 'propagation_latency' | 'event_counts' | 'resource_usage'; + fromStep?: StepId; + }>[]; + readonly cleanup: Readonly<{ kind: 'run_scoped'; verifyEmpty: true }>; + readonly budget: ExecutionBudget; + readonly sharing: 'isolated'; +} + +export interface AuditProfileV1 { + readonly schemaVersion: 1; + readonly id: ProfileId; + readonly title: string; + readonly parameters: Readonly>; + readonly caseTimeoutMs: Milliseconds; +} + +export interface CompiledCasePlanV1 { + readonly schemaVersion: 1; + readonly contractId: ContractId; + readonly contractDigest: Sha256Digest; + readonly caseDefinitionDigest: Sha256Digest; + readonly profileId: ProfileId; + readonly runId: RunId; + readonly coordinate: Readonly<{ + caseId: CaseId; + transport: DdpTransport; + topology: MongoTopology; + observerOrder: NonEmptyReadonlyArray; + seed: UInt32; + faultId?: FaultId; + }>; + readonly resolvedParameters: Readonly>; + readonly steps: NonEmptyReadonlyArray; + readonly budget: ExecutionBudget; + readonly digest: Sha256Digest; +} diff --git a/reliability/contracts/index.ts b/reliability/contracts/index.ts new file mode 100644 index 0000000..839ebf4 --- /dev/null +++ b/reliability/contracts/index.ts @@ -0,0 +1,3 @@ +export type * from './audit.js'; +export type * from './declarative.js'; +export type * from './primitives.js'; diff --git a/reliability/contracts/primitives.ts b/reliability/contracts/primitives.ts new file mode 100644 index 0000000..2a9b1f5 --- /dev/null +++ b/reliability/contracts/primitives.ts @@ -0,0 +1,38 @@ +/** + * Compile-time primitives for the change-stream audit contract. + * + * Brands prevent accidental cross-assignment inside typed code. They do not + * validate untrusted runtime values; a future boundary parser must do that. + */ +export type Brand = Value & { + readonly __brand: Name; +}; + +export type AuditId = Brand; +export type CaseId = Brand; +export type CapabilityId = Brand; +export type ContractId = Brand; +export type EvidenceEntryId = Brand; +export type FaultId = Brand; +export type HarnessRevision = Brand; +export type LedgerId = Brand; +export type OracleId = Brand; +export type ParameterName = Brand; +export type ProfileId = Brand; +export type RunId = Brand; +export type Sha256Digest = Brand; +export type StepId = Brand; + +export type ByteCount = Brand; +export type Milliseconds = Brand; +export type PositiveInteger = Brand; +export type UInt32 = Brand; + +export type EjsonScalar = null | boolean | number | string; + +export type EjsonValue = + | EjsonScalar + | readonly EjsonValue[] + | { readonly [key: string]: EjsonValue }; + +export type NonEmptyReadonlyArray = readonly [Value, ...Value[]]; diff --git a/specs/2026-09-08-change-stream-audit-contract-foundation.md b/specs/2026-09-08-change-stream-audit-contract-foundation.md index 62371ff..e4837ec 100644 --- a/specs/2026-09-08-change-stream-audit-contract-foundation.md +++ b/specs/2026-09-08-change-stream-audit-contract-foundation.md @@ -1,8 +1,11 @@ # Change-stream audit contract foundation -Status: accepted for initial implementation -Date: 2026-09-08 -Project: `performance` +Status: accepted for initial implementation + +Date: 2026-09-08 + +Project: `performance` + Project root: `/Users/leonardo/Repositories/performance` ## Problem @@ -461,18 +464,18 @@ coordination is required. - [x] Select `upstream/main` as the clean feature base. - [x] Preserve the discarded feature tip in a local backup ref. -- [ ] Add branded primitives and exact identity contracts. -- [ ] Add coordinate, capability, evidence, cleanup, and result contracts. -- [ ] Add authored-case and compiled-plan contracts. -- [ ] Add a type-only public export surface. -- [ ] Add strict TypeScript configuration. -- [ ] Add compile-time positive and negative examples. -- [ ] Run the contract typecheck. -- [ ] Run the existing JavaScript unit suite. -- [ ] Confirm the final diff contains no runtime audit implementation. -- [ ] Make `origin/main` exactly match `upstream/main`. -- [ ] Force-push the reduced feature branch. -- [ ] Verify both remote ref hashes. +- [x] Add branded primitives and exact identity contracts. +- [x] Add coordinate, capability, evidence, cleanup, and result contracts. +- [x] Add authored-case and compiled-plan contracts. +- [x] Add a type-only public export surface. +- [x] Add strict TypeScript configuration. +- [x] Add compile-time positive and negative examples. +- [x] Run the contract typecheck. +- [x] Run the existing JavaScript unit suite. +- [x] Confirm the final diff contains no runtime audit implementation. +- [x] Make `origin/main` exactly match `upstream/main`. +- [x] Force-push the reduced feature branch. +- [x] Verify both remote ref hashes. ## Acceptance criteria diff --git a/tests/types/change-stream-audit-contracts.ts b/tests/types/change-stream-audit-contracts.ts new file mode 100644 index 0000000..5479eb3 --- /dev/null +++ b/tests/types/change-stream-audit-contracts.ts @@ -0,0 +1,153 @@ +import type { + AuditCaseOutcome, + ByteCount, + CaseDefinitionV1, + CaseId, + EvidenceEntryId, + EvidenceReference, + LedgerId, + Milliseconds, + OracleId, + ParameterName, + PositiveInteger, + Sha256Digest, + StepId, + UInt32, +} from '../../reliability/contracts/index.js'; + +const caseId = 'event.insert' as CaseId; +const stepId = 'subscribe' as StepId; +const oracleId = 'snapshot.matches' as OracleId; +const ledgerId = 'mongodb' as LedgerId; +const digest = 'digest' as Sha256Digest; +const seed = 1 as UInt32; +const duration = 1_000 as Milliseconds; +const count = 1 as PositiveInteger; +const payloadBytes = 1_024 as ByteCount; +const parameterName = 'documents' as ParameterName; + +const caseDefinition = { + schemaVersion: 1, + id: caseId, + title: 'Insert reaches one subscriber', + source: 'design review', + rationale: 'Proves the smallest end-to-end change-stream behavior.', + applicability: [ + { + topologies: ['replica_set'], + transports: ['sockjs'], + observerOrders: [['changeStreams', 'oplog']], + }, + ], + parameters: { + [parameterName]: { + type: 'integer', + default: 1, + minimum: 1, + maximum: 10, + }, + }, + fixture: { + collection: 'reliabilityDocuments', + publication: 'reliability.documents', + generator: 'minimal-document', + subscribers: { kind: 'literal', value: 1 }, + documents: { kind: 'parameter', name: parameterName }, + payloadBytes: { kind: 'literal', value: 64 }, + }, + preconditions: [ + { kind: 'actual_observer_available', driver: 'changeStreams' }, + ], + steps: [ + { + id: stepId, + kind: 'subscribe', + query: { kind: 'unordered' }, + clients: { kind: 'fixture', field: 'subscriberIds' }, + onFailure: 'fail_case', + }, + ], + evidence: { + requiredProducers: ['mongodb', 'ddp_client', 'meteor_probe'], + observer: { + kind: 'selected', + driver: { kind: 'coordinate', field: 'observerOrder' }, + }, + transportIdentity: 'required', + fault: null, + ledgers: [ledgerId], + }, + oracles: [ + { + id: oracleId, + family: 'snapshot_exact', + producer: 'mongodb', + expected: { kind: 'fixture', field: 'documents' }, + observed: { producer: 'mongodb', stepId, ledgerId }, + failureReason: 'mongodb_snapshot_mismatch', + gate: 'hard', + }, + ], + diagnostics: [{ kind: 'propagation_latency', fromStep: stepId }], + cleanup: { kind: 'run_scoped', verifyEmpty: true }, + budget: { + maximumSteps: count, + maximumDocuments: count, + maximumSubscribers: count, + maximumPayloadBytes: payloadBytes, + maximumEvidenceEntries: count, + stepTimeoutMs: duration, + caseTimeoutMs: duration, + maximumRetries: 0, + }, + sharing: 'isolated', +} satisfies CaseDefinitionV1; + +void caseDefinition; +void digest; +void seed; + +const passingOutcome = { + status: 'passed', + reasons: [], +} satisfies AuditCaseOutcome; + +void passingOutcome; + +const observedReference = { + producer: 'ddp_client', + ledgerId, + entryId: 'entry-1' as EvidenceEntryId, + stepId, +} satisfies EvidenceReference<'ddp_client'>; + +void observedReference; + +// @ts-expect-error A passing result cannot include failure reasons. +const invalidPassingOutcome: AuditCaseOutcome = { + status: 'passed', + reasons: ['unexpected event'], +}; + +void invalidPassingOutcome; + +const invalidObservedReference: EvidenceReference<'mongodb'> = { + // @ts-expect-error Observed evidence cannot claim expected-model provenance. + producer: 'expected_model', + ledgerId, + entryId: 'entry-2' as EvidenceEntryId, + stepId, +}; + +void invalidObservedReference; + +const invalidOrderedQuery: CaseDefinitionV1['steps'][number] = { + id: stepId, + kind: 'subscribe', + // @ts-expect-error An ordered query requires an explicit, non-empty sort. + query: { kind: 'ordered' }, + clients: { kind: 'fixture', field: 'subscriberIds' }, + onFailure: 'fail_case', +}; + +void invalidOrderedQuery; diff --git a/tsconfig.audit-contracts.json b/tsconfig.audit-contracts.json new file mode 100644 index 0000000..a8848bd --- /dev/null +++ b/tsconfig.audit-contracts.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "NodeNext", + "moduleDetection": "force", + "moduleResolution": "NodeNext", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "strict": true, + "target": "ES2022", + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true + }, + "include": [ + "reliability/contracts/**/*.ts", + "tests/types/change-stream-audit-contracts.ts" + ] +} From 77a4862b74f92fde24e76fab70609874a4d7dc09 Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Tue, 8 Sep 2026 23:54:05 -0400 Subject: [PATCH 3/4] docs(audit): focus scope on change streams --- ...change-stream-audit-contract-foundation.md | 80 +++++++++++++++---- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/specs/2026-09-08-change-stream-audit-contract-foundation.md b/specs/2026-09-08-change-stream-audit-contract-foundation.md index e4837ec..2340123 100644 --- a/specs/2026-09-08-change-stream-audit-contract-foundation.md +++ b/specs/2026-09-08-change-stream-audit-contract-foundation.md @@ -11,12 +11,12 @@ Project root: `/Users/leonardo/Repositories/performance` ## Problem Meteor needs a correctness audit that can eventually prove change-stream, -oplog, polling, publication, DDP, recovery, and cleanup behavior across a -bounded matrix. The first implementation attempted to deliver that entire -system at once. Its final form added 82 files and roughly 17,900 lines on top -of the benchmark platform, including an executable harness, owned MongoDB and -Meteor processes, a raw DDP client, fixture instrumentation, a declarative -interpreter, a large case catalog, validators, and tests. +publication, DDP, recovery, and cleanup behavior across a bounded matrix. The +first implementation also modeled Meteor's legacy oplog and polling observer +drivers. Its final form added 82 files and roughly 17,900 lines on top of the +benchmark platform, including an executable harness, owned MongoDB and Meteor +processes, a raw DDP client, fixture instrumentation, a declarative interpreter, +a large case catalog, validators, and tests. That implementation supplied useful design evidence, but it crossed too many boundaries for an initial review. This change establishes only the durable @@ -90,6 +90,7 @@ implementation. Create a small, reviewable contract package that: - names the closed dimensions of an audit coordinate; +- fixes change streams as the only observer implementation under audit; - models authored cases as discriminated unions; - separates expected state from independently observed evidence; - represents capability support and applicability without a case catalog; @@ -118,6 +119,7 @@ Create a small, reviewable contract package that: - Case catalogs, profiles, generated data, and negative-control catalogs. - Runtime or integration tests. - Dashboard and result-writer integration. +- Oplog-driver, polling-driver, or observer-fallback correctness coverage. - Any claim that the future audit is executable. ```text @@ -139,8 +141,8 @@ Create a small, reviewable contract package that: - TypeScript contracts are design-time guidance, not a trust boundary. - Data entering from JSON, processes, sockets, databases, or Meteor must be treated as `unknown` until a later runtime-validation layer is implemented. -- A case executes against exactly one transport, topology, observer order, - profile, seed, release identity, and harness revision. +- A case executes against exactly one transport, topology, profile, seed, + release identity, and harness revision, using the change-stream driver. - Evidence producers are independent enough that expected-model output cannot masquerade as observed system evidence. - Cleanup is part of correctness, not a best-effort epilogue. @@ -152,7 +154,8 @@ The following choices remain intentionally open: - the serialization format and runtime schema library; - the exact first set of executable cases; - whether a compiler consumes authored objects, JSON, or generated definitions; -- how Meteor exposes authoritative observer and fallback evidence; +- how Meteor exposes authoritative change-stream selection and lifecycle + evidence; - how sharded-cluster and multi-instance environments are owned; - which identities belong in benchmark results versus separate audit artifacts; - whether the benchmark dashboard should ingest correctness results. @@ -160,6 +163,48 @@ The following choices remain intentionally open: These uncertainties do not prevent agreement on the boundary shapes. They do prevent treating the shapes as a final compatibility promise. +## Product boundary + +MongoDB change streams are the supported application-facing API. They still use +the replica-set oplog as replication infrastructure, so oplog retention and +resume-token availability remain environmental facts. The audit does not +exercise Meteor's separate oplog-tailing observer driver. + +```text + MongoDB implementation detail + ============================= + + replica-set oplog + | + | supplies history + v + AUDIT BOUNDARY ---> MongoDB Change Stream API + | + v + Meteor change-stream driver + | + v + publication + DDP + | + v + client-observed state + + In scope: everything from the Change Stream API boundary downward + Metadata: oplog-window facts that constrain resume-token availability + Excluded: Meteor's legacy direct oplog-tailing observer implementation +``` + +Meteor 3.5 makes change streams the first-choice reactivity driver, while still +documenting oplog and polling as fallbacks. This audit intentionally has a +narrower goal than Meteor's compatibility matrix: prove the change-stream path +or report that its prerequisites were unavailable. + +Primary references: + +- +- +- + ## Contract model ### Layering @@ -230,15 +275,16 @@ runtime boundary. +-----------+----------+----------+-----------+ | | | | v v v v - transport topology observerOrder seed - sockjs replica_set [changeStreams, uint32 - sockjs-polling standalone oplog, ...] + transport topology observer seed + sockjs replica_set changeStreams uint32 + sockjs-polling standalone uws sharded_cluster ``` The type system closes the vocabulary but cannot enforce numeric bounds, -non-empty arrays, uniqueness, or a valid observer fallback order. Those are -documented invariants for future runtime validation. +non-empty arrays, uniqueness, or whether the actual observer is the requested +change-stream driver. Those are documented invariants for future runtime +validation. ### Authored case @@ -357,7 +403,7 @@ a new named type; it does not widen the existing version with optional fields. The initial implementation must encode these invariants: - discriminants select the valid fields for every value reference, parameter, - precondition, step, observer expectation, and outcome; + precondition, step, change-stream expectation, and outcome; - expected-model evidence is distinct from system-observed evidence; - a plan contains resolved values, not unresolved authored parameters; - case and run results carry exact coordinate and identity objects; @@ -376,7 +422,7 @@ The following require future runtime validation: - maximum collection sizes and nesting depth; - step ordering and barrier closure; - plan and ledger digest correctness; -- release, topology, observer, and cleanup attestation truth. +- release, topology, change-stream, and cleanup attestation truth. ```text TypeScript can prove Runtime must prove @@ -490,6 +536,8 @@ coordination is required. client, application probe, case catalog, or generated definition remains. 7. `origin/main` resolves to the same commit as `upstream/main`. 8. `origin/feat/change-stream-audit` resolves to the reduced branch tip. +9. Audit contracts cannot express oplog or polling as observer implementations; + those names appear only in historical context and explicit exclusions. ## Review order From c57a0ae0df392c63b1af97996fbb60f7b84b42e8 Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Tue, 8 Sep 2026 23:57:20 -0400 Subject: [PATCH 4/4] refactor(audit): remove legacy observer scope --- reliability/contracts/audit.ts | 21 ++----- reliability/contracts/declarative.ts | 55 ++++--------------- ...change-stream-audit-contract-foundation.md | 11 +++- tests/types/change-stream-audit-contracts.ts | 37 ++++++++++--- 4 files changed, 55 insertions(+), 69 deletions(-) diff --git a/reliability/contracts/audit.ts b/reliability/contracts/audit.ts index 97b88ac..2e6987a 100644 --- a/reliability/contracts/audit.ts +++ b/reliability/contracts/audit.ts @@ -19,16 +19,15 @@ import type { UInt32, } from './primitives.js'; -export type ObserverDriver = 'changeStreams' | 'oplog' | 'polling'; +export type ChangeStreamObserver = 'changeStreams'; export type DdpTransport = 'sockjs' | 'sockjs-polling' | 'uws'; -export type MongoTopology = 'replica_set' | 'sharded_cluster' | 'standalone'; +export type MongoTopology = 'replica_set' | 'sharded_cluster'; export interface CaseCoordinate { readonly caseId: CaseId; readonly transport: DdpTransport; readonly topology: MongoTopology; - /** Ordered, non-empty, and unique after runtime validation. */ - readonly observerOrder: NonEmptyReadonlyArray; + readonly observer: ChangeStreamObserver; /** An unsigned 32-bit integer after runtime validation. */ readonly seed: UInt32; readonly faultId?: FaultId; @@ -37,14 +36,10 @@ export interface CaseCoordinate { export interface ApplicabilityScope { readonly topologies: NonEmptyReadonlyArray; readonly transports: NonEmptyReadonlyArray; - readonly observerOrders: NonEmptyReadonlyArray< - NonEmptyReadonlyArray - >; } export type CapabilityExpectation = | 'supported' - | 'fallback_required' | 'not_supported' | 'out_of_scope'; @@ -56,7 +51,7 @@ interface CapabilityDefinitionBase { export type CapabilityDefinition = | (CapabilityDefinitionBase & { - readonly expectation: 'supported' | 'fallback_required'; + readonly expectation: 'supported'; readonly requiredCases: NonEmptyReadonlyArray; readonly applicability: NonEmptyReadonlyArray; }) @@ -104,7 +99,7 @@ export interface HarnessIdentity { export interface MongoMemberIdentity { readonly name: string; - readonly role: 'primary' | 'secondary' | 'mongos' | 'config' | 'standalone'; + readonly role: 'primary' | 'secondary' | 'mongos' | 'config'; } interface MongoEnvironmentIdentityBase { @@ -121,9 +116,6 @@ export type MongoEnvironmentIdentity = | (MongoEnvironmentIdentityBase & { readonly topology: 'sharded_cluster'; readonly clusterName: string; - }) - | (MongoEnvironmentIdentityBase & { - readonly topology: 'standalone'; }); export interface AuditIdentity { @@ -186,8 +178,7 @@ export type OracleFamily = | 'event_absent' | 'revision_monotonic' | 'field_absent' - | 'observer_identity' - | 'fallback_identity' + | 'change_stream_identity' | 'transport_identity' | 'session_identity' | 'fault_witness' diff --git a/reliability/contracts/declarative.ts b/reliability/contracts/declarative.ts index b5afbfa..8cb9041 100644 --- a/reliability/contracts/declarative.ts +++ b/reliability/contracts/declarative.ts @@ -1,10 +1,10 @@ import type { ApplicabilityScope, + ChangeStreamObserver, DdpTransport, EvidenceProducer, MongoTopology, ObservedEvidenceProducer, - ObserverDriver, OracleFamily, } from './audit.js'; import type { @@ -31,7 +31,7 @@ export type DeclarativeValueReference = | Readonly<{ kind: 'parameter'; name: ParameterName }> | Readonly<{ kind: 'coordinate'; - field: 'seed' | 'transport' | 'topology' | 'observerOrder'; + field: 'seed' | 'transport' | 'topology' | 'observer'; }> | Readonly<{ kind: 'run'; field: 'runId' }> | Readonly<{ kind: 'fixture'; field: 'documents' | 'subscriberIds' }> @@ -59,23 +59,8 @@ export type DeclarativeSelector = value: DeclarativeValueReference; }>; -export interface DeclarativeSortField { - readonly field: string; - readonly direction: 'ascending' | 'descending'; -} - export type DeclarativeQuery = | Readonly<{ kind: 'unordered' }> - | Readonly<{ - kind: 'ordered'; - sort: NonEmptyReadonlyArray; - }> - | Readonly<{ - kind: 'windowed'; - sort: NonEmptyReadonlyArray; - skip: DeclarativeValueReference; - limit: DeclarativeValueReference; - }> | Readonly<{ kind: 'selector'; selector: DeclarativeSelector; @@ -87,12 +72,7 @@ export type DeclarativeQuery = | Readonly<{ kind: 'multiple_projections'; projections: NonEmptyReadonlyArray>; - }> - | Readonly<{ - kind: 'unsupported_selector'; - operator: 'json_schema'; - }> - | Readonly<{ kind: 'change_stream_unavailable' }>; + }>; export type DeclarativeMutation = | Readonly<{ @@ -213,14 +193,7 @@ export type FaultController = | 'watch_setup_pause'; export type CasePrecondition = - | Readonly<{ - kind: 'actual_observer_available'; - driver: ObserverDriver; - }> - | Readonly<{ - kind: 'observer_driver_unavailable'; - driver: ObserverDriver; - }> + | Readonly<{ kind: 'change_stream_available' }> | Readonly<{ kind: 'topology_available' | 'topology_matches_coordinate'; topology: MongoTopology; @@ -234,21 +207,15 @@ export type CasePrecondition = controller: FaultController; }>; -export type ObserverEvidenceRequirement = - | Readonly<{ - kind: 'selected'; - driver: DeclarativeValueReference; - }> - | Readonly<{ - kind: 'fallback'; - from: ObserverDriver; - to: ObserverDriver; - reasonRequired: true; - }>; +export interface ChangeStreamEvidenceRequirement { + readonly driver: ChangeStreamObserver; + readonly selectionEvidence: 'required'; + readonly lifecycleEvidence: 'required' | 'diagnostic'; +} export interface CaseEvidenceRequirements { readonly requiredProducers: NonEmptyReadonlyArray; - readonly observer: ObserverEvidenceRequirement; + readonly changeStream: ChangeStreamEvidenceRequirement; readonly transportIdentity: 'required' | 'diagnostic'; readonly fault: Readonly<{ kind: 'activated_and_restored'; @@ -330,7 +297,7 @@ export interface CompiledCasePlanV1 { caseId: CaseId; transport: DdpTransport; topology: MongoTopology; - observerOrder: NonEmptyReadonlyArray; + observer: ChangeStreamObserver; seed: UInt32; faultId?: FaultId; }>; diff --git a/specs/2026-09-08-change-stream-audit-contract-foundation.md b/specs/2026-09-08-change-stream-audit-contract-foundation.md index 2340123..d842530 100644 --- a/specs/2026-09-08-change-stream-audit-contract-foundation.md +++ b/specs/2026-09-08-change-stream-audit-contract-foundation.md @@ -120,6 +120,8 @@ Create a small, reviewable contract package that: - Runtime or integration tests. - Dashboard and result-writer integration. - Oplog-driver, polling-driver, or observer-fallback correctness coverage. +- Standalone MongoDB, ordered observers, and selectors that Meteor routes away + from its change-stream driver. - Any claim that the future audit is executable. ```text @@ -277,8 +279,8 @@ runtime boundary. v v v v transport topology observer seed sockjs replica_set changeStreams uint32 - sockjs-polling standalone - uws sharded_cluster + sockjs-polling sharded_cluster + uws ``` The type system closes the vocabulary but cannot enforce numeric bounds, @@ -537,7 +539,10 @@ coordination is required. 7. `origin/main` resolves to the same commit as `upstream/main`. 8. `origin/feat/change-stream-audit` resolves to the reduced branch tip. 9. Audit contracts cannot express oplog or polling as observer implementations; - those names appear only in historical context and explicit exclusions. + those names appear only in historical context, explicit exclusions, and a + compile-time rejection fixture. +10. Coordinates exclude standalone MongoDB, and authored queries exclude + ordered/windowed observers and deliberately unsupported selectors. ## Review order diff --git a/tests/types/change-stream-audit-contracts.ts b/tests/types/change-stream-audit-contracts.ts index 5479eb3..f0c5c7b 100644 --- a/tests/types/change-stream-audit-contracts.ts +++ b/tests/types/change-stream-audit-contracts.ts @@ -1,6 +1,7 @@ import type { AuditCaseOutcome, ByteCount, + CaseCoordinate, CaseDefinitionV1, CaseId, EvidenceEntryId, @@ -36,7 +37,6 @@ const caseDefinition = { { topologies: ['replica_set'], transports: ['sockjs'], - observerOrders: [['changeStreams', 'oplog']], }, ], parameters: { @@ -56,7 +56,7 @@ const caseDefinition = { payloadBytes: { kind: 'literal', value: 64 }, }, preconditions: [ - { kind: 'actual_observer_available', driver: 'changeStreams' }, + { kind: 'change_stream_available' }, ], steps: [ { @@ -69,9 +69,10 @@ const caseDefinition = { ], evidence: { requiredProducers: ['mongodb', 'ddp_client', 'meteor_probe'], - observer: { - kind: 'selected', - driver: { kind: 'coordinate', field: 'observerOrder' }, + changeStream: { + driver: 'changeStreams', + selectionEvidence: 'required', + lifecycleEvidence: 'required', }, transportIdentity: 'required', fault: null, @@ -144,10 +145,32 @@ void invalidObservedReference; const invalidOrderedQuery: CaseDefinitionV1['steps'][number] = { id: stepId, kind: 'subscribe', - // @ts-expect-error An ordered query requires an explicit, non-empty sort. - query: { kind: 'ordered' }, + // @ts-expect-error Ordered observers are outside the change-stream audit. + query: { kind: 'ordered', sort: [{ field: '_id', direction: 'ascending' }] }, clients: { kind: 'fixture', field: 'subscriberIds' }, onFailure: 'fail_case', }; void invalidOrderedQuery; + +const invalidLegacyObserver: CaseCoordinate = { + caseId, + transport: 'sockjs', + topology: 'replica_set', + // @ts-expect-error The audit has no legacy oplog-driver coordinate. + observer: 'oplog', + seed, +}; + +void invalidLegacyObserver; + +const invalidStandaloneTopology: CaseCoordinate = { + caseId, + transport: 'sockjs', + // @ts-expect-error Standalone MongoDB cannot provide change streams. + topology: 'standalone', + observer: 'changeStreams', + seed, +}; + +void invalidStandaloneTopology;