Release: merge development into beta - #865
Open
github-actions[bot] wants to merge 8 commits into
Open
github-actions[bot] wants to merge 8 commits into
github-actions[bot] wants to merge 8 commits into
Conversation
…60912202740 chore(release): 0.2.7-unstable.20260912202740
github-actions
Bot
requested review from
WilcoLouwerse,
bbrands02 and
rjzondervan
as code owners
September 13, 2026 17:04
…260913184323 chore(sync): carry beta back into development
…API and frontend (#870) * feat(session): declare the session schema with every property it has to carry First of the five session specs. Inert by design: nothing reads these schemas yet, so this is safe to merge on its own. Session and SessionTurn take over every property of Conversation and Message, copied verbatim from the LIVE schemas rather than transcribed, because the migration compares field for field and a property on one side and not the other drops data silently: conversation -> title, userId, agentId, metadata, talkRoomToken, participants, talkRoomOrigin message -> role, content, sources, context, authorId, authorDisplayName Session also gains `triggerOrigin` (human|cron|event|flow, default human). The Chat page splits human sessions from automated ones on it, so without it every scheduled run turns up in somebody's chat list. Every property now carries a title AND a description, because the Skills page sources its header tooltips from descriptions and a schema shipped without them cannot grow that later. THE SLUG STAYS `agentsession`, AND THE GATE IS WHY. Task 1.2 says to stop if slug `session` resolves to another app's schema. Measured: it returns scholiq's id 336, "a scheduled occurrence of a Cohort meeting", and `hermiq/session` is not found at all. But hermiq's session schema already exists as `agentsession`, and this register already prefixes for exactly this reason (agentskill, agentbudget, agentwebhook, agentaifeature). Declaring the bare slug would walk into the collision; the prefix avoids it, and the chain's goal is one vocabulary in the UI, the API and the code, none of which a storage slug is. Conversation and Message stay declared and untouched. Nothing is removed until the migration is verified in production. VERIFIED BY IMPORT, not by absence of an error. A schema that fails to import VANISHES and the failure is only logged, so this was confirmed to EXIST afterwards: forced the re-import locally, then read the schemas back from the instance. agentsession v0.2.0, 10 properties, triggerOrigin default human, enum present agentsessionturn v0.2.0, 9 properties Demo data gains the split the spec asks for: two human sessions and one cron, with real titles instead of the "Voorbeeld Title 1/2/3" placeholders, so a fresh install has something in each group to render. Recorded in design.md for the migration spec to start from: the machine-derived property lists, and two measurements it needs. The archived-read path does NOT work (`_includeDeleted=true` moves the total 1 -> 10 and still returns 1 row, the flag reaches the count query only), so the migration must read through `ObjectEntityMapper::findDeletedAcrossAllMagicTables()` or silently leave 9 of 10 conversations behind. And the real baseline on this instance is 10 conversations (1 live, 9 archived) and 7 messages, not the 282 the spec assumes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(session): migrate conversations onto the session schema, and prove it Second of the five session specs, and the only one that can lose data. Copy semantics: sources are never modified or deleted, so rollback is deleting the copies. Nothing reads sessions yet, so this is still safe to merge alone. Measured on this instance, 10 conversations and 7 messages, not the 282 the spec assumes: run 1 sessions: 10 copied, 0 already present, 0 failed turns: 7 copied, 0 already present, 0 failed run 2 sessions: 0 copied, 10 already present, 0 failed turns: 0 copied, 7 already present, 0 failed conversation 10 (9 archived) -> agentsession 10 (9 archived) message 7 (6 archived) -> agentsessionturn 7 (6 archived) Verifier clean, 0 mismatches. Sources compared field for field against a pre-migration snapshot: 0 modified. Rollback exercised four times. FOUR DEFECTS IN THIS STEP, ALL SILENT, ALL FOUND BY VERIFYING RATHER THAN BY THE COUNTS. 1. `ObjectService::find()` returned the SOURCE conversation when asked for a session with the same uuid. The one live conversation was reported "already present" and never migrated, while nine archived ones were. The step now checks the ANSWER's schema, and reads through the mapper with register and schema given explicitly. 2. `saveObject()` ignores `@self.deleted`. All nine archived conversations arrived LIVE, with no error: the chat list would have filled with threads the user had archived and the Archive tab would have been empty. That is the exact symptom the spec's task 3.4 names. 3. It also stamps `_owner` from the acting identity and re-dates `_created`/`_updated` to now, so a March thread claimed to be written today and every row landed as `__system__` rather than `admin`. Impersonating the owner around the call was tried and did not take. (2) and (3) are why `carryProvenance()` copies owner, organisation, created, updated and the archive marker as columns afterwards. That is the one place this step leaves the object API, and it is deliberate: the spec requires those to survive EXACTLY, and soft-deleting the copy instead would rewrite `deletedAt`/`deletedBy` to today and to this migration, losing when and by whom something was really archived. 4. `ObjectService::find()` excludes soft-deleted rows, so on a re-run every archived session read as absent and was rewritten. The second run reported "8 copied, 2 already present" when all ten existed, which is the overwrite the idempotency guarantee forbids, because it would undo an edit made through the UI afterwards. The existence check now passes `includeDeleted: true`. Two more traps worth naming. `RegisterMapper` has no `findBySlug()` — its `find()` takes `string|int` and resolves either — and calling the method that does not exist threw, was caught, and became a silent "could not resolve" that read as "no filter needed". And the entities `findDeletedAcrossAllMagicTables()` returns do not carry `deleted` hydrated even though the query selects `*`, so the marker is read from the column rather than the entity. The archived read is the whole reason the step is shaped this way: `_includeDeleted=true` moves the reported total from 1 to 10 and still returns 1 row, because the flag reaches the count query only. Reading through `ObjectService` alone migrates 1 of 10. `verify.sql` ships with the change. Its positive control was run first, as the spec requires: un-archiving one migrated object and changing its title made it report exactly that uuid and nothing else. A verifier that has never failed is not evidence. `RegisterScopedSchema` is extracted rather than inlined: resolving a slug inside one register is the thing this whole chain works around, and `session` and `agent` both resolve to another app's schema globally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(session): move the API and every reader onto the session schema Third of the five session specs, and the breaking one. `/api/sessions/*` is the surface now; `/api/conversations/*` stays as a deprecated alias so no existing integration 404s on deploy. THE SPEC READS AS THOUGH THE CONTROLLER IS THE READER. IT IS NOT. 14 files reference the `conversation` schema: the Talk bridge (TalkRoomBinding, TalkSessionRoom, TalkTurnService, TalkBotInvokeListener), the engine (Engine, ConversationManagementHandler, ConversationTitleWriter, MessageHistoryHandler), AssistantService, ContextAgentInteractionService, ScheduleService and three controllers. Moving only the controller would have split the app in half: the UI writing sessions while Talk, the engine and the scheduler kept reading conversations, with neither half erroring. They all move together. 15 schema constants and 10 field sites, each checked in context rather than swept: - `CONVERSATION_SCHEMA`/`MESSAGE_SCHEMA` values -> agentsession/agentsessionturn (15) - filters and object writes keyed `conversationId` -> `sessionId` (9) - one LITERAL `setSchema('message')` that the constants would have missed - `deleteRelatedObjects()` now picks the key per schema: a turn points at its session through `sessionId`, Feedback still uses `conversationId` because feedback objects are not part of this rename. Filtering both on one key matches nothing for one of them, and here that means deleting a session and orphaning its turns. Deliberately NOT changed: request params, response keys and log context that happen to be spelled `conversation`/`message`. Those are the API contract the frontend reads, and the frontend does not move until the next spec. `serializeMessage()` bridges it — reads the stored `sessionId`, emits `sessionId` AND `conversationId` — so the Chat page keeps working today and has something to move onto tomorrow. TWO RUNTIME FAILURES THAT ONLY CALLING THE ROUTES COULD FIND. Renaming the class left the aliases pointing at `conversation#index`. That is not a startup failure: the app loaded, `/api/sessions` answered 200, and every `/api/conversations/*` path threw `Could not resolve ConversationController` as a runtime 500. This is exactly what task 4.1 warns about, and it happened anyway. Then, once repointed, POST returned 405: a Nextcloud route name is `app.controller.action`, so both families registering `session#create` collided silently. The aliases carry `'postfix' => 'legacy'`, which is the supported way to point two paths at one method — and they ARE one method, not copies, so the two families cannot drift. All 16 routes across both families verified at 200, the whole archive lifecycle on each. The deprecation log fires, so retirement can be decided on traffic rather than optimism. TALK WAS ENABLED FOR THIS, AND IT MATTERED. `occ app:enable spreed` failed on a missing `vendor/`, so the four Talk room-ownership e2e tests had been skipping wherever spreed is absent, which is CI and was this instance. They run now, and all four pass — the first time they have ever executed in this repo. Moving the Talk bridge to a new schema with those tests still skipping would have been unverifiable. 1904 unit tests pass. 14 fixtures encoded the old vocabulary and were updated; each was read in context first, because a fixture named `'message'` may be a schema slug or a request parameter and only one of those moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(session): finish the frontend rename, and split human from automated The API and the schema moved to "session" in the previous two commits; this moves what a user reads and fixes the six defects the rename was blocked on. ## The split needed a backend change first `triggerOrigin` was never serialised. Grouping the list on a field the API does not return renders identically whether the split works or matches nothing, which is the failure the spec warns about, so the serialiser now returns it. A session stored before the property existed reports `human` rather than nothing: every one of them was started by a person, and a session in neither group has disappeared. ## The six defects - The new-session control was a no-op with no thread open, so it read as broken. It now moves focus to the start surface, which is a change the user can see. - The start surface centred its children ON its own scroll container. Once the content is taller than the column that pushes the first row above the scroll origin, where scrolling cannot reach it. Measured at a 420px window: the heading sat at -85.5px and the first agent card at -1px, and `scrollTop` will not go below 0. Centring now lives on an inner block whose auto margins collapse to 0 when the content overflows. - Rows show an origin icon, the agent's name and the time, not a bare date. - The single archive button is now a menu: Continue, Archive, Delete. The permanent-delete endpoint never required an archived session, so Delete works from both tabs; the modal's docblock said "archived" and now does not. - Active splits into "Started by you" and "Started automatically". An unrecognised origin falls to the human group on purpose: treating anything that is not `human` as automated would hide a real chat behind a heading nobody thinks to look under the first time a new origin ships. ## The stored title was still the old word The stream endpoint wrote the literal title "New conversation", which the list renders verbatim. It now writes "New session", and the placeholder detector accepts both: recognising only the new spelling would leave every existing row permanently unnamed, and nothing about such a row looks different when it does. ## Catalogues Dutch gained 36 entries and lost the 31 the rename left dead. Three chip labels that had shipped untranslated are translated. Four user-facing strings lost their em-dashes, per the writing skill's rule 8. ## Tests `chat.spec.ts` asserts the split in both directions, because two presence assertions alone pass a filter that lets everything through. Three other e2e specs asserted strings or a schema path this rename moved; one of them (`chat-conversation-row`) had already been dead since the testid was renamed. The migration's `@spec` tags pointed at a spec file that did not exist, and its container lookups declared their optionality only through the shape of each catch; both are fixed. * chore(session): tag the renamed chat methods with their spec, and satisfy phpcs on touched lines Gate 16 found 22 changed frontend methods without @SPEC; each now names the session-surface requirement it implements. The three legacy route aliases that ran past 150 characters are split like their neighbours (the routes array is unchanged), and the touched test lines use named arguments. * chore(session): tag the delete modal watcher and name the logger mock argument --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
remko48 and
rubenvdlinde
as code owners
September 14, 2026 09:03
… kept (#872) * docs(openspec): the hermiq umbrella for competitor parity wave 3 Records where the five wave-3 changes come from: cluster 46 of the round 4 competitor sweep plus C-intake-30, and decisions D13, D6, D21 and D17. Also records what hermiq already ships against the cluster, and why the command language stays with openregister. States no requirement of its own, so it carries skip_specs. * docs(openspec): a provider and a place per AI feature Cluster 46, C-integrations-38 (must) and C-configuration-75. Joins tenant-model-policy to ai-feature-governance: a feature binds its own provider inside the policy ceiling, a provider declares its residency, a run outside a required residency is refused before the call, and the run records which model saw what and where. * docs(openspec): what the model reads and what is kept Cluster 46, C-access-and-privacy-3 and -53, both must. A feature may require filinq's redaction and fails closed without it; a detection never counts as a redaction. Every run carries a retention, a job enforces it and reports that it ran, and deletion removes the payload while leaving the audit chain whole. * docs(openspec): the declared tool surface and the prompt library Cluster 46, C-integrations-19 and C-configuration-44. Adds the outbound direction beside agent-tool-governance: an outside agent calls declared tools under its own principal's rights, with a per-registration grant and an output allowlist. Makes the assistant prompts administered objects with a one-act kill switch. * docs(openspec): a conversational intake that files for the citizen Cluster 46, C-intake-9, C-communication-64 and C-integrations-1, none with a driven passer. A surface separate from the tool-free case assistant, with a create-only grant, a classification that can abstain, no dead ends, one conversation across channels, and a deterministic escalation signal preferred over a model one. * docs(openspec): identical reports collapse into one C-intake-30, a must the sweep puts in dossiq's cluster 35. hermiq answers whether two reports describe one event, in three bands, additively and reversibly, with readable reasons. dossiq decides what a group means. Grouping never reduces the confirmations of receipt owed under Awb 4:3a.
…stry (#875) * docs(openspec): adopt the connection registry * feat(connections): declare six outside connections for integriq's registry * feat(connections): an Integrations page over integriq's app_connection rows * feat(connections): report the AI provider, web search, GitHub store and webhook delivery to integriq * test(e2e): the Integrations page against integriq's registry, written and not run
… must keep (#877) The port of feat/retire-bespoke-github-sync onto OpenRegister's FederatedConfigService was redone against development and stopped: every store operation would lose behaviour that 22 later commits added, and most of it can only be restored inside OpenRegister. This adds wire-level tests over a recording broker and a fake GitHub for that behaviour, each watched failing with its guard broken, and an OpenSpec change recording the eight gaps. No production code changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.