diff --git a/AGENTS.md b/AGENTS.md index 9049e255..ecf6f337 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,13 @@ This repo's load-bearing primitives are documented as concept cells in `Concept-Verified: ` in the commit message. This satisfies the validator without requiring a separate `last_verified` bump. +## Changelog + +There is no `CHANGELOG.md`. Do not create one, and do not add a changelog entry +as part of a PR — every parallel PR editing the same "Unreleased" section +guarantees merge conflicts and resets CI on unrelated branches. Release notes +are generated at release time from commit/PR history instead. + ## Development Commands ### Build & Test diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a360d724..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,417 +0,0 @@ -# Changelog - -All notable changes to AMPERE are recorded here. Dates are in UTC. - -The project is pre-1.0; breaking changes are acceptable and explicitly called out. - -## [Unreleased] - -### Fixed - -- **`BatchIssueCreator` reported `success = true` on a cyclic batch** - ([AMPR-322](https://linear.app/miley/issue/AMPR-322)). - - `topologicalSort` detected a back-edge and bare-returned, silently dropping - the edge: the batch was created in an order that violated its own declared - `dependsOn`/`parent` edges, with no error, no log, and `success = true`. - A cyclic batch is now refused before anything is created: `created` is - empty, `success = false`, and `errors` holds one - `IssueCreateError.dependencyCycle(path)` entry whose new `cyclePath` field - (`["a", "b", "a"]`, empty on every other error) names the closed walk. The - order is never "repaired". A DAG batch is unchanged. - -- **Android: the ampere-core database could never be created** - ([AMPR-324](https://linear.app/miley/issue/AMPR-324)). - - The schema declares three FTS5 virtual tables (`knowledge_chunks_fts`, - `KnowledgeFts`, `OutcomeMemoryFts`) and Android's system SQLite ships - FTS3/FTS4 but not FTS5. `SQLiteOpenHelper.onCreate` runs in a transaction, so - the failing `CREATE VIRTUAL TABLE` rolled the *entire* schema back: the - database was never created, no version row was written, and every subsequent - open retried and failed identically. This took down far more than search — - `LinkStore`, `KnowledgeStore`, memory and the persisted event bus all threw - `SQLiteException: no such module: fts5` on every read and write. The tables - have been in the schema since `76762eba`, so the database had most likely - never initialized on Android. - - `createAndroidDriver` now opens the database through a bundled SQLite build - that has FTS5 compiled in (`com.osmerion.sqlite.android:sqlite-android`, - the maintained continuation of `requery/sqlite-android`), which keeps FTS5 - semantics identical to iOS and desktop/JVM. This adds ~1.2–1.8 MB of native - code per ABI to Android consumers. - - It also opens the database eagerly and wraps any failure in a new - `AmpereDatabaseInitializationException`, so a schema that cannot be created - fails at driver construction with a diagnosable message instead of surfacing - as an opaque per-query exception that callers log as a skip. - -### Added - -- **`ampereSqliteOpenHelperFactory()` (Android)** - ([AMPR-324](https://linear.app/miley/issue/AMPR-324)). - - Consumers that construct their own `AndroidSqliteDriver` rather than calling - `createAndroidDriver` must pass this as the driver's `factory` argument; - SQLDelight's default `FrameworkSQLiteOpenHelperFactory` uses the system - SQLite and reintroduces the failure above. - -- **`CanonRecurrence`, plus `recurrence` on `CanonReminder`/`CanonCalendarEvent` - and `CanonReminder.startsAt`** ([AMPR-319](https://linear.app/miley/issue/AMPR-319)). - - A bounded recurrence value type in `ampere-core`: `every` (a wall-clock - `kotlin.time.Duration`), bounded by `count` or `until`, built through - `CanonRecurrence.of(...): Result`. Deliberately not RFC - 5545 — an `RRULE` carries calendar vocabulary (`BYDAY`, `BYSETPOS`) that no - canon provider round-trips honestly. Bounding is a write-side factory, never - a `require` in `init`, so an out-of-range recorded value still decodes and a - trace stays replayable; `isWithinBounds` exposes the rule. - - It lands on `CanonReminder` and `CanonCalendarEvent`, where Apple Reminders - and Apple Calendar both already produce recurrence through - `EKRecurrenceRule`. It was considered and **declined** on `CanonWorkItem`: - Linear's recurrence is a template that materializes separate issues, Jira's - is a clone, and GitHub Issues has none, so the field would have been the - canon's first justified by a consumer's need rather than provider evidence. - The split that fell out — a work item is the work, a reminder is its - schedule — is recorded in `docs/concepts/domain-canon.md`. `startsAt` on - `CanonReminder` mirrors EventKit's `startDateComponents`. All three fields - default to `null`, so entities serialized before this change decode - unchanged. - - Four-consumer note: `every` is a span and survives a game or simulation - clock; `until` is wall-clock and does not. A `count`-bounded recurrence is - the portable form. - -- **`CanonWorkItem.dependsOn`, `CanonWorkGraph`, and `SequenceProbe`** - ([AMPR-322](https://linear.app/miley/issue/AMPR-322)). - - `dependsOn: List` is the canon's first work-item→work-item edge. - It clears the intersection gate without a ruling — a blocking dependency is - provider-native on Linear (`blockedBy`/`blocks`), Jira (`issuelinks` of - type *Blocks*), and GitHub (sub-issues) — and defaults to empty, so - entities serialized before this change decode unchanged. Ids are same-Link - `CanonId`s; a missing referent is a graph error, not a serialization error. - No Linear/Jira/GitHub binding module exists yet; the provider fields are - recorded in the KDoc for when one lands. - - `CanonWorkGraph` (project + milestones + items) is the first canon - *composite* value type, assembled by the caller from one Link. It has no - `init` guard on purpose: a cyclic plan must be constructible so it can be - recorded and then convicted. `SequenceProbe` (`ProbeId("ampere.sequence")`) - is the first shipped `Probe`: `Violated("dangling dependsOn: a -> ghost")` - for referential integrity, `Violated("cycle: a -> b -> c -> a")` for the - first cycle found, `Holds` otherwise — never `Warn`, never `Undetermined`. - `ProbeRegistry.registerAmpereProbes()` registers it for Oscilloscope - listings. Timing invariants stay Socket-side (decision D17). - -- **Probe SPI: `Probe`, four-valued `Verdict`, `ProbeSuite`, open - `ProbeRegistry`** ([AMPR-318](https://linear.app/miley/issue/AMPR-318)). - - New `link.socket.ampere.probe` package in `ampere-core`. A Probe is a - predicate over a static artifact — a plan graph, a manifest, a recalled - fact — not a grader of a trace. `S` is deliberately unconstrained - (the `PerceiveSource` precedent), so consumers can probe types Ampere never - imports. `Verdict` is fixed at four values: `Holds`, `Warn` (decided, bad, - not disqualifying), `Violated`, and `Undetermined` (not decided — carries a - machine-readable `UndeterminedCause` so "no published spec" and "page needed - a JS engine" route to different remedies; never renders as a soft pass). - `ProbeRegistry` is open (modeled on `ToolRegistry`, not the closed - `ArcRegistry`) and exists for discovery/observability, not dispatch. No - Probe implementations ship in this change. - -### Changed - -- **`PlugManifest.isCanonExternal` now exempts `emits` only** - ([AMPR-320](https://linear.app/miley/issue/AMPR-320)). - - The flag was documented as "no canon-level data contract at all", and - `PlugManifestValidator` skipped the empty-scope and undeclared-scope rules - wholesale for a canon-external Plug — so `isCanonExternal = true` alongside - `optionalConsumes = {PLACE}` passed unchecked. The settled semantics: the - flag says a Plug's *observations* are outside canon; `consumes` and - `optionalConsumes` stay subject to scope validation regardless, because a - Plug emitting non-canon types can still take canon in (Socket's Blueprint - Plug uses a `CanonPlace` for its region when a planner offers one). - `LinkRequirement.minimumScope` is now checked against - `consumes ∪ optionalConsumes ∪ (emits unless canon-external)`, and - `CanonExternalWithDeclaredCanon` fires on `emits` only. A canon-external - Plug that consumes no canon keeps the AMPR-260 carve-out unchanged, so every - landed canon-external Plug (Clipboard, Vision OCR, Notify, Web) validates as - before. Socket's `NativePlugCatalogTest` re-implements these rules and needs - the same change — filed as - [SCKT-603](https://linear.app/miley/issue/SCKT-603) (Socket Phase 2a). - -- **Apple binding: `recurrenceRules` is no longer a dropped field** - ([AMPR-319](https://linear.app/miley/issue/AMPR-319)). - - `AppleCanonBindingRegistry` listed `recurrenceRules` as lossy for `REMINDER` - — a true statement that is now false. `EKRecurrenceRule` frequency ∈ {daily, - weekly, monthly, yearly} × `interval` maps to `every`, - `EKRecurrenceEnd.recurrenceEndWithOccurrenceCount` to `count`, and - `recurrenceEndWithEndDate` to `until`. What stays lossy runs the other way, - canon → EventKit — a sub-daily `every` has no EventKit frequency, and - EventKit's day/set-position selectors have no canon expression — and is - recorded as a residual note on a carried field rather than as a dropped - field. `eventKitAlarms`, `priority`, and `subtasks` are unchanged. - -- **Breaking (with alias): eval `Probe` renamed `EvalCase`** - ([AMPR-318](https://linear.app/miley/issue/AMPR-318)). - - The eval harness's `Probe` data class was an eval *case*, and its name - blocked the Probe SPI above. `Probe` → `EvalCase`, `ProbeResult` → - `EvalCaseResult`, `ProbeSeed` → `EvalSeed`, each with a - `@Deprecated` typealias so existing code compiles with a warning only. - `BenchEvent.ProbeGraded` keeps its name — it is serialized into recorded - traces, and renaming it would make them undecodable. - -## [0.13.0] — 2026-08-01 - -### Added - -- **`CanonWorkItem.description`** ([AMPR-269](https://linear.app/miley/issue/AMPR-269)). - - Deferred at admission (AMPR-262) for failing the bulk rule — a GitHub issue - body routinely exceeds the 32 KiB per-projection budget, and `WORK_ITEM` was - the wave's strongest admission candidate. `CanonProse` (AMPR-268) now gives - it a bounded shape, so the field lands additively with a `null` default: a - bounded snippet, not the provider's full description, which continues to - survive losslessly in `CanonProvenance.nativePayload`. Three provider - formats collapse to one field — Jira's ADF (a structured JSON tree, not - text), GitHub-flavoured Markdown, and Linear's Markdown — each losing - structure a flattening to prose cannot carry back; named in KDoc alongside - the wave's other lossy fields. `CanonProject.summary` already moved to - `CanonProse` as part of AMPR-268, so no further migration was needed there. - Pinned by a maximally-filled `CanonWorkItem` in the existing - `CanonWorkEntitiesTest` projection-budget assertion. - -- **Knowledge-work canon wave: `WORK_ITEM`, `PROJECT`, `MILESTONE`, `TABLE`** - ([AMPR-262](https://linear.app/miley/issue/AMPR-262)). - - The canon's first deliberate reopening since v1, admitting the vocabulary Arcs - need to participate in projects, tasks, and tabular data. All four are Ring 3 - — they arrive only over `Mcp`, `OAuthRest`, or `FolderMount` Links — bringing - the canon to 33 types. Each admission is recorded against a four-gate bar - (noun / intersection / producer / bulk) in - `docs/ampr-262-knowledge-work-canon-wave.md`, with per-provider lossy - fields named in KDoc on day one. - - Also added: `CanonWorkStatus` (a coarse `BACKLOG`/`TODO`/`IN_PROGRESS`/`DONE`/ - `CANCELLED` lifecycle plus verbatim `providerStatus`, mirroring - `CanonServiceStatus`), and `CanonTablePreview`, whose `bounded()` factory caps - a table preview at 5 rows × 12 columns × 120 characters so bulk rows resolve - out of band rather than riding the entity. - - `SPREADSHEET` and `ROADMAP` were assessed and rejected, `INITIATIVE` deferred - with a recorded re-admission trigger; the rationale is in `domain-canon.md`. - - **Additive.** No existing type, field, or wire name changes, and no exhaustive - `when` over `CanonType` or `CanonEntity` exists in the codebase — external - consumers who wrote one will see a new-member exhaustiveness error, which is - the intended tripwire. - -- **`CanonAssetRef` widened to out-of-band content** - ([AMPR-262](https://linear.app/miley/issue/AMPR-262), - [AMPR-258](https://linear.app/miley/issue/AMPR-258)). - - Documentation only — no shape or wire-name change. `CanonAssetRef` now names - where any out-of-band content lives, not only visual media, and backs - `CanonTable.contentRef`. This resolves the non-visual case AMPR-258 deferred - in favour of reuse over a sibling primitive, so consent enforcement stays in - one place (`ConsentEnforcingAssetResolver`) rather than forking with a second - resolver hierarchy. - -- **Canon cross-reference contract settled** ([AMPR-266](https://linear.app/miley/issue/AMPR-266)). - - Documentation and tests only — no shape or wire-name change. The nullable - `CanonId` cross-reference shape shared by `CanonEmailMessage.mailboxId`, - `CanonWorkItem.projectId`, `CanonMilestone.projectId`, and - `CanonTable.documentId` is now a formalized invariant in - `domain-canon.md`: `null` is ambiguous between "not attached" and "provider - didn't say," a `CanonId` only resolves against entities from the *same* - Link, and there is no referential integrity. `mailboxId` — the field the - other three copied without KDoc — is documented for the first time. A - resolver SPI and a breaking typed-reference wrapper were both costed and - rejected: neither `CanonId` nor anything else in the repo maps back to the - `SourceHandle` a resolver would need, so either option is new-store - machinery speculatively built for no confirmed consumer, not a cheap mirror - of `AssetResolver`. Pinned in `CanonCrossReferenceContractTest`. - -- **`FolderRef` alongside `CredentialRef` on `Link`** ([AMPR-261](https://linear.app/miley/issue/AMPR-261)). - - A folder mount's reference is now a first-class part of `Link` instead of - side-channel state, mirroring `CredentialRef`'s shape. Revoked folder refs - wire into the resolution gate's existing revocation precedence via a new - `RevocationScope.FOLDER`. - -- **`PlugManifest.optionalConsumes`** ([AMPR-259](https://linear.app/miley/issue/AMPR-259)). - - Lets a Plug (e.g. Vision OCR, SCKT-436) declare a canon type it can use if - available without requiring it. `PlugManifestValidator` counts - `optionalConsumes` toward the `UndeclaredCanonScope` subset check and - rejects a canon type declared in both `consumes` and `optionalConsumes`. - -- **`PlugManifest.isCanonExternal` carve-out** ([AMPR-260](https://linear.app/miley/issue/AMPR-260)). - - Canon-external Plugs (`emits={}`, `consumes={}`) had no - `LinkRequirement.minimumScope` that could pass `EmptyLinkRequirementScope` - and `UndeclaredCanonScope` together. `isCanonExternal` is now an explicit - positive declaration that skips both rules, mirroring Socket's - `NativePlugDescriptor.isCanonExternal`, plus a new - `CanonExternalWithDeclaredCanon` check so the flag can't be used to - silently exempt a mis-declared canon-bearing Plug. - -- **Size budget and drop policy for trace recording** ([AMPR-267](https://linear.app/miley/issue/AMPR-267)). - - Trace recording was unbounded: `Channel.UNLIMITED` buffering, an - unconstrained `events_json` TEXT column, and no hygiene test. Enforces a - per-event (truncate-and-flag) and per-trace (drop-with-a-marker) byte - budget at `RecordingHandle.stop()`, the single chokepoint every event - passes through, so an already-persisted trace always stays decodable via - `Event.serializer()`. Also bounds the recorder's buffering channel as a - producer-side backstop. - -- **`TableWriteIntent` core primitives and `PlugManifest` capability gating** - ([AMPR-263](https://linear.app/miley/issue/AMPR-263)). - - Following the AMPR-263 write-back verdict (append-row and update-cell as - provider-delegated intents, no whole-table replace): adds - `TableWriteIntent`/`RowRef`/`Capability`/`Failure` and a `TableWriteSink` - guard base that enforces the capability gate before any dispatch, plus a - matching `PlugManifest.tableWriteCapabilities` declaration and validator - rules. - -### Breaking - -- **Bounded prose in canon: `CanonProse` replaces raw `String` prose fields** - ([AMPR-268](https://linear.app/miley/issue/AMPR-268)). - - `CanonDocument.plainText` predated the bulk rule admitted by AMPR-262 — an - unbounded `String?` that could blow the 32 KiB per-projection budget on its - own. `CanonProse` brings it, and every other free-form prose field the recon - flagged, under the same rule `CanonTablePreview` established for tables: a - `bounded()` factory truncates to 8,000 UTF-16 units (never splitting a - surrogate pair) and sets `truncated` rather than rejecting at decode time — - a canon type must always decode, so bounding stays a write-side concern. - - Changed from `String`/`String?` to `CanonProse`/`CanonProse?`: - - `CanonDocument.plainText` - - `CanonMessage.bodyText` - - `CanonNote.bodyText` - - `CanonJournalEntry.bodyText` - - `CanonProject.summary` - - `CanonEmailMessage.bodyText` and `CanonEmailDraft.bodyText` are **not** - changed — both already have a live write-back adapter - (`MailMessageAdapter`) that round-trips the raw string through - `NativePayload.fields`, and truncating a field an adapter writes back would - silently corrupt provider data. That decision is scoped to this ticket, not - a permanent exemption. - - **Migration for external consumers:** callers constructing these fields - must switch to `CanonProse.bounded(text)`. A recorded trace containing the - old wire shape (`"plainText": "..."` as a raw JSON string) will fail to - decode against the new shape (`"plainText": {"text": "...", "truncated": - false}`). No migration tooling exists for already-recorded traces — trace - hygiene is out of scope here (tracked separately) and none of the affected - types has shipped a production adapter yet, so no real trace is known to - carry the old shape. - -- **PROPEL `CognitivePhase` enum is now canonically six members** - ([AMPR-172](https://linear.app/miley/issue/AMPR-172)). - - `CognitivePhase` in - `link.socket.ampere.agents.domain.cognition.sparks.PhaseSpark.kt` - becomes the full PROPEL cycle in canonical order: - - ```kotlin - enum class CognitivePhase { - PERCEIVE, - RECALL, - OBSERVE, - PLAN, - EXECUTE, - LEARN, - } - ``` - - Previously the enum carried only `PERCEIVE / PLAN / EXECUTE / LEARN`, - silently dropping `RECALL` and `OBSERVE`. The acronym is now load-bearing: - `enumValues().toList()` yields the cycle in order. - - **Migration for external consumers:** - - Any `when (phase: CognitivePhase)` site without an `else` branch will - fail to compile with an exhaustiveness error. Add explicit branches - for `RECALL` and `OBSERVE`. - - Code that iterated `CognitivePhase.entries` will now see six phases - instead of four. Test matrices that assumed four-phase coverage will - automatically extend; tests that hardcoded a four-element list need - updating. - - Declarative spark `.spark.md` frontmatter that previously enumerated - `"phases": ["PERCEIVE", "PLAN", "EXECUTE", "LEARN"]` continues to - parse, but the spark will not apply during `RECALL` or `OBSERVE`. If - full coverage is intended, update the list to all six phases. - - Serialized values are unchanged: existing `"PERCEIVE"` / `"PLAN"` / - `"EXECUTE"` / `"LEARN"` strings still deserialize. AMPERE does not - persist `CognitivePhase` across runs today, so no data migration is - required. - -- **CLI `AmperePhosphorBridge` removes the `LEARN → EVALUATE` paveover - ([AMPR-182](https://linear.app/miley/issue/AMPR-182)).** - Phosphor 0.6.2 ships with [PHO-28](https://linear.app/miley/issue/PHO-28), - adding `OBSERVE` and renaming `EVALUATE → LEARN` to align with the - canonical PROPEL phases. The bridge now maps directly: - `PERCEIVE → PERCEIVE`, `RECALL → RECALL`, `OBSERVE → OBSERVE`, - `PLAN → PLAN`, `EXECUTE → EXECUTE`, `LEARN → LEARN`. - The `CognitiveChoreographer`, `CognitivePalette`, and related rendering - surfaces updated to reference the canonical phases. - -### Added - -- `PhaseSpark.Recall` and `PhaseSpark.Observe` built-in sparks with - default `promptContribution` strings tuned for memory-recall and - state-monitoring behavior, respectively. `PhaseSpark.forPhase` covers - all six members. - -### Notes - -- Phosphor 0.6.2 ([PHO-28](https://linear.app/miley/issue/PHO-28)) now - aligns with canonical PROPEL phases: `PERCEIVE / RECALL / OBSERVE / PLAN / EXECUTE / LEARN / LOOP / NONE`. - -## [0.12.0] — 2026-07-31 - -### Added - -- `CanonAssetRef` and `AssetResolver` for canon asset references - ([AMPR-258](https://linear.app/miley/issue/AMPR-258)). -- `NativeFields` cursor, `NativeSchema` value class, and child provenance - rules ([AMPR-248](https://linear.app/miley/issue/AMPR-248)). -- `PlugPermission.DeviceCapability` and `NativeAuthorizationStatus` - ([AMPR-249](https://linear.app/miley/issue/AMPR-249)). -- `PerceiveSource`/`ExecuteSink` chassis operation layer - ([AMPR-246](https://linear.app/miley/issue/AMPR-246)). -- `ampere-core-test-fixtures` published as its own Maven artifact - ([AMPR-250](https://linear.app/miley/issue/AMPR-250)). - -### Changed - -- Binding declarations split out of `ampere-core` into edge modules - ([AMPR-257](https://linear.app/miley/issue/AMPR-257)). -- `CanonAdapter` split into `Readable`/`Writable`/`CreatingCanonAdapter` - ([AMPR-247](https://linear.app/miley/issue/AMPR-247)). -- Thin Canon types widened for the P0 Plug wave - ([AMPR-252](https://linear.app/miley/issue/AMPR-252)). -- Plug manifest dead ends closed - ([AMPR-251](https://linear.app/miley/issue/AMPR-251)). -- iOS framework link moved out of Xcode's nested Gradle invocation - ([AMPR-253](https://linear.app/miley/issue/AMPR-253)). -- iOS CI job time reduced via simulator boot caching/overlap - ([AMPR-244](https://linear.app/miley/issue/AMPR-244), - [AMPR-255](https://linear.app/miley/issue/AMPR-255)). - -### Fixed - -- Dokka link-resolution and Kotlin/Native cast warnings. - -## [0.6.0] — 2026-05 - -Released; see `git log v0.6.0` for the commit history.