From a9b1f788c984725696bfa97eb95a411296583710 Mon Sep 17 00:00:00 2001 From: Miley Chandonnet Date: Wed, 2 Sep 2026 19:55:17 -0500 Subject: [PATCH] Remove CHANGELOG.md; tell agents not to recreate it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every recent PR added an entry to the same Unreleased section in CHANGELOG.md. There was no explicit rule telling agents to do this — they were pattern-matching the file's own structure and the git history of prior PRs. With many PRs open in parallel, this guaranteed merge conflicts and reset CI on unrelated branches whenever one PR merged. No CI gate or build script depended on the file's presence. Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 7 + CHANGELOG.md | 417 --------------------------------------------------- 2 files changed, 7 insertions(+), 417 deletions(-) delete mode 100644 CHANGELOG.md 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.