Skip to content

Companion Suite: entire cloud backlog (#1–#4) — core, phone task mode, engine Worker, desktop scaffold, P0 kit#6

Open
rulingAnts wants to merge 25 commits into
fix/make-app-workfrom
claude/dekereke-core-package-3uya8a
Open

Companion Suite: entire cloud backlog (#1–#4) — core, phone task mode, engine Worker, desktop scaffold, P0 kit#6
rulingAnts wants to merge 25 commits into
fix/make-app-workfrom
claude/dekereke-core-package-3uya8a

Conversation

@rulingAnts

@rulingAnts rulingAnts commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Implements all four HANDOFF.md backlog items, the P0 verification kit, and the day's decisions (D3–D10). Based on fix/make-app-work (PR #5). All six CI jobs green across both workflows on the head commit.

1. packages/dekereke_core — pure Dart engine (174 tests)

Codec + model (lossless, verbatim unknown/nested fragments) · canonical form with byte-identical UTF-16⇄UTF-8 round-trips (doc/canonical_form.md) · settings model with machine-local split · identity ladder + sidecar map · record/field 3-way merge with plain-language conflicts · object-level diffRecords (tracked changes are never line-based) · audio manifest + dedupe · content-addressed BlobStore (D5) with hash-verified sync planning · health-panel rules · Reference block allocation (D4) · deterministic .dektask/.dekresult (doc/task_packages.md).

2. Phone app task mode (plan §5.4)

.dektask import (entries keep DkSyncIDs; reference audio/pictures unpacked) → config-driven elicitation (visible prompts, playable WAV/FLAC references, writable text/audio columns with suffix-named 16-bit mono WAV takes) → validated Export My Answers .dekresult. DB v3 additive migration. 45 pre-existing tests green + 24 new. Smoke-test with the CI APK + test_data/sample_task/sample.dektask.

3. workers/dekereke-sync — the engine Worker (backlog #3, D5)

Cloudflare Worker + D1, engine only: owner bootstrap (idempotent), one-time invite mint/claim (atomic NULL-guarded claim; same-install retry OK), owner approval, and the desired/reported two-lane relay with CAS revisions (flextext patterns throughout). Secrets stored as SHA-256 only. Strictly additive migrations. 16 integration tests on real workerd+D1 via Miniflare; new worker-engine CI job. No R2 binding on purpose — user blobs live in owner storage.

4. apps/companion — desktop scaffold (backlog #4, D6)

Flutter for Windows sharing dekereke_core. The Health check screen already works: open a Dekereke .xml and the core's health rules run against it (settings + audio folder picked up when adjacent). History (P1) and Sync (P2) are plain-language placeholders. Path-filtered companion.yml workflow builds a runnable Windows app: Actions → Companion desktop → dekereke-companion-windows artifact (unzip, run dekereke_companion.exe).

5. P0 verification kit

test_data/p0_test_kit/CHECKLIST.md — 25 click-by-click steps (~25 min) on the Windows VM settling all seven P0 items + the pinned-build choice, with probe TestDB, Update-From-File probe, and octave-paired test tones. Generated by committed deterministic tools.

Decisions recorded (docs/HANDOFF.md)

D3 WAV-everywhere (FLAC only DB→phone reference) · D4 block defaults · D5 engine-only on maintainer CF + owner-supplied Drive/R2 storage · D6 Flutter Windows · D8 canonical preserves record order · D9 conflict UI first-class · D10 general-purpose product scope.

For Seth

  • Phone test: CI APK artifact + test_data/sample_task/ README.
  • Windows VM: run the P0 checklist when you have ~25 min.
  • Desktop preview: download dekereke-companion-windows from the Companion workflow run, unzip, run the exe, open the P0 kit's TestDB.xml in Health check — the planted problems should appear in plain language.

Incidents worth knowing

  • Bare audio gitignore pattern silently swallowed lib/src/audio/ (one red run) — sample-data ignores now anchored to the repo root.
  • Repo-wide flutter analyze broke when sub-projects joined — the app job now analyzes only its own lib/ test/; each sub-project has its own job.
  • windows-latest (2025 image) can't locate Visual Studio for Flutter — desktop build pinned to windows-2022.
  • Session initially had no GitHub write access; fixed by Seth mid-session.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5

…#1 begins)

Pure Dart package with the proven encoding detection ported from
lib/services/xml_service.dart, extended into a lossless codec:

- model: value fields (untrimmed text; presence-only booleans as
  present-and-empty) + fragment fields (unknown/nested XML kept verbatim)
- canonical form (UTF-8/LF, deterministic, one field per line) with exact
  inverse to the UTF-16 LE/BOM/CRLF working format; spec in
  doc/canonical_form.md; byte-identity round-trip proven against
  test_data/dekereke_fixtures/synthetic_db.xml
- SoundFile cell split (P0 #7 caveat documented) + suffix filename rule
- decision D8 recorded: canonical form preserves file record order

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
Copilot AI review requested due to automatic review settings July 2, 2026 12:41
…plan §4.1)

Every settings element is preserved verbatim (unknown variants included).
Typed views for the verified shared settings: TAB-separated column→suffix
mappings (irregular suffix styles covered), column order/widths, hidden/
analysis columns, syllable division, banned onsets.

splitMachineLocal() empties the four verified machine-local path elements
in place (order-stable, no local data leaves the machine) and hands them
back separately; mergeMachineLocal() reassembles a machine's real file —
split+merge round-trip is byte-identical on the fixture, as is plain
parse→encode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces packages/dekereke_core as a pure-Dart shared core for the Companion Suite, including a Dekereke XML ↔ model codec with a deterministic canonical form and CI coverage to keep the core package analyzable/tested independently from Flutter.

Changes:

  • Added a new pure-Dart package implementing encoding sniffing/decoding, database parsing, deterministic canonical rendering, and working-format materialization.
  • Added a focused test suite (encoding, codec round-trip invariants, SoundFile helpers) backed by synthetic fixtures.
  • Updated docs/plans and CI to incorporate the new core package and its “preserve file order” canonical-form decision.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/dekereke_core/test/sound_file_test.dart Adds tests for SoundFile cell splitting and suffixing helpers.
packages/dekereke_core/test/encoding_test.dart Adds tests for XML encoding sniffing and decoding/encoding helpers.
packages/dekereke_core/test/db_codec_test.dart Adds extensive round-trip, canonical/working form, and preservation tests using synthetic fixtures.
packages/dekereke_core/README.md Documents package purpose, module status, and development commands.
packages/dekereke_core/pubspec.yaml Defines the new package, SDK constraint, and dependencies.
packages/dekereke_core/lib/src/model/sound_file.dart Implements SoundFile cell splitting and suffix filename derivation.
packages/dekereke_core/lib/src/model/database.dart Introduces immutable database/record/field model with order preservation.
packages/dekereke_core/lib/src/codec/xml_writer.dart Implements pinned, deterministic XML escaping and node serialization utilities.
packages/dekereke_core/lib/src/codec/encoding.dart Implements BOM/sniff-based UTF-8/UTF-16 LE/BE detection + encoding helpers.
packages/dekereke_core/lib/src/codec/db_codec.dart Implements Dekereke DB parse/render and canonical/working representations.
packages/dekereke_core/lib/dekereke_core.dart Public exports for the new package APIs.
packages/dekereke_core/doc/canonical_form.md Adds the canonical-form specification describing invariants/normalizations.
packages/dekereke_core/analysis_options.yaml Enables strict analyzer settings for the package.
docs/HANDOFF.md Records decision D8 (preserve file order in canonical form).
docs/COMPANION_SUITE_PLAN.md Updates plan text to reflect D8 and point to the canonical-form spec/impl.
.github/workflows/ci.yml Adds a dedicated dekereke-core job and ensures nested package resolution during Flutter analyze.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +81 to +84
case XmlProcessing():
buffer.write(node.value.isEmpty
? '<?${node.target}?>'
: '<?${node.target} ${node.value}?>');
Comment on lines +10 to +14
Uint8List _fixtureBytes() {
// Tests run with CWD = package root; the fixtures live at the repo root.
final path = '../../$_fixturePath';
return File(path).readAsBytesSync();
}
claude added 13 commits July 2, 2026 12:47
…adder (plan §4.2)

DkSyncID lives in a synced sidecar (deksync-identity JSON, deterministic
rendering), never in the XML. reconcileIdentity() re-binds IDs at every
save down the ladder: content hash → SoundFile → Reference+Gloss →
position (flagged needsReview). IDs are restored, never re-minted, when
any signal matches; new records mint 128-bit hex IDs; vanished entries
surface for user-confirmed deletion; duplicated rows keep the ID on the
best (position-ordered) match and flag the copy as a near-duplicate.

Content hash = SHA-256 of the record's canonical rendering, so identical
content hashes identically on every machine.

Exhaustive scenario tests: adoption, unchanged, edit, edit+re-anchor,
total rewrite, reorder, reorder+edit, delete, delete+insert, duplicate
rows, duplicate References, blank Reference+Gloss, fixture cycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…4.2b)

Reference is display-only in merge. Additions kept from both sides
(same-Reference additions coexist); one-sided field edits and identical
edits auto-merge; divergent edits produce FieldConflict objects carrying
reference/gloss/base/ours/theirs for the plain-language UI, with the
merged output provisionally holding ours. Absent ≠ empty throughout, so
presence-only booleans merge correctly.

Deletions are never implicit: one-sided deletes keep the record and
surface a PendingDeletion (with a delete-vs-edit flag); both-sided
deletes leave the output but are still reported; applyConfirmedDeletions
applies what the user confirms. applyConflictResolution applies keep-
ours/keep-theirs/custom/remove choices. identifyRecords bridges from
reconcileIdentity to merge input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…ge (plan §4.4)

audio-manifest.json maps filename → {sha256, bytes} with deterministic
rendering (sorted, stable bytes for change-gating); sha256 doubles as the
content-addressed R2 key. duplicateGroups() is the dedupe report the
first manifest build doubles as.

Merge mirrors the record merge: one-sided re-records win; same-name
different-content on both sides is an AudioConflict resolved by the
plan's keep-both policy (resolveKeepBoth renames the incoming file,
refusing to clobber); removals are never implicit (PendingAudioRemoval +
applyConfirmedRemovals, remove-vs-re-record flagged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…#1 complete

Deterministic ZIP formats: task.json carries the DkSyncID map (aligned by
position with the canonical-UTF-8 wordlist.xml — IDs never enter the XML,
per D2), field config (visible/playable/writable, text/audio/both, suffix
assignments) and consent config; result.json carries values, recordings
(named <base><suffix>.wav) and the consent log.

validateResult() returns plain-language problems (wrong task/checkpoint,
unknown records, non-writable columns, missing files); applyResultValues()
builds the theirs side for the 3-way merge-back, tested end-to-end against
merge3 (clean case + researcher-edited-the-same-cell conflict).

Format spec in doc/task_packages.md. 132 tests. HANDOFF backlog updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…ource

The bare 'audio' ignore pattern (meant for the repo-root sample folder)
matched packages/dekereke_core/lib/src/audio/ too, so the audio manifest
implementation never made it into the previous commits and CI failed on
the head commit while local tests (which read the on-disk file) passed.
Anchor the sample-data patterns to the repo root and commit manifest.dart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
- D3 OVERTURNED to WAV everywhere: no FLAC at rest or between databases;
  new recordings always 16-bit mono WAV; single exception = reference
  audio DB→phone in .dektask (playback-only)
- D4: Companion picks Reference-block defaults
- D5 REVISED: maintainer's Cloudflare account hosts the engine only
  (Worker+D1 enrollment/metadata/relay, flextext model, no user blobs);
  owner-supplied storage via one pluggable content-addressed blob-store
  interface with BOTH backends from day one: owner's Google Drive
  (API-only, immutable sha256-named append-only blobs) and owner's own R2
- D6: Flutter Windows desktop confirmed
- D9: assume overlapping edits — conflict UI is first-class
- D10: general-purpose product scope; Seth's Fayu project is not the
  driving deployment; typical owner starts from a local Windows folder

Plan §3/§4.4/§4.5/§5.2/§5.4/§6/§7 updated accordingly; task_packages.md
notes the audio-format rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…lt export (backlog #2)

The app now consumes dekereke_core (path dependency). Importing a
.dektask replaces the wordlist with the task's subset — entries keep
their DkSyncIDs (task.json ID map, D2) — stores the task config, and
unpacks bundled reference audio (task_audio/) and pictures. Plain-XML
import returns the app to normal mode.

Elicitation becomes task-driven when a task is active: visible prompt
fields (first one large), playable reference columns resolved by the
suffix rule (bundled as WAV or FLAC — D3's one exception), and writable
columns collecting text and/or 16-bit mono WAV recordings named
<SoundFile base><suffix>.wav. One value per cell is stored in new
task_values/task_recordings tables (DB v3, additive migration).

Export produces a .dekresult (values + recordings + consent log),
checked with the core validateResult before writing so a bad archive
can never reach the researcher.

Records with empty/duplicate References are skipped at task import with
a plain-language message (this app keys entries by Reference); audio
columns without a suffix are rejected outright.

All 45 pre-existing tests stay green; 24 new task tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…dows VM

test_data/p0_test_kit/ covers all seven P0 items: unknown flat + nested
probe tags (survival through grid/save/Update-From-File), deliberately
out-of-order records (does save rewrite order?), auto-backup filename
capture, file-locking/external-edit behavior, built-in recorder spec
(empty-SoundFile target + suffix-mapped column), and pipe vs comma
multi-file cells with octave-paired test tones so answers are audible.

Files are generated by the committed dekereke_core tool
(tool/generate_p0_kit.dart) using the core codec itself — genuine
UTF-16 LE + BOM + CRLF output, deterministic, regenerable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…history summaries

Record/field diff keyed on DkSyncID: added/removed records, per-field
changes (absent vs cleared kept distinct, fragments flagged), and a
pure re-sort reported as orderChanged rather than as edits. Powers the
'Saved by Seth — 3 words changed' checkpoint summaries and per-record
tracked changes. Plan §4.2b now states the object-vs-lines principle
explicitly: git/GitHub is storage/transport only; line-based merge is
never invoked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
test_data/sample_task/sample.dektask: 6 words, visible Gloss/Indonesian
prompts, playable reference tones (distinct pitches; two records have
none on purpose), and a writable text+audio column with the -yoh suffix.
Generated by the committed tool (generate_sample_dektask.dart); the WAV
tone generator is factored into tool/wav_util.dart, shared with the P0
kit generator (P0 kit regenerates byte-identically).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…§4.2, D4)

checkDatabaseHealth() implements the Reference lint: duplicate References
(one finding listing every twin), empty References, SoundFile↔Reference
mismatches (per file of multi-file cells), missing audio files, missing
suffix files (only when the mapped column has data), and orphaned audio —
each with severity and a plain-language message. Suffix variants count as
accounted so mapped recordings never show as orphans.

ReferenceAllocation implements D4: deterministic block grants (smallest
s ≡ 1 mod size above every block and the database's highest number —
thousands ranges with the default size), numeric-aware next-label
assignment with zero padding, and a versioned synced JSON file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…n §4.4, D5)

BlobStore is the pluggable owner-storage interface (D5): immutable
sha256-keyed blobs with hash verification on every put/get, so
corruption is caught at the hop where it happened and never spreads.
MemoryBlobStore covers tests/dry-runs; FileBlobStore (write-then-rename,
hash-named flat files) covers the local cache and USB/folder seeding.
Google Drive and owner-R2 become drop-in implementations desktop-side.

planAudioSync/runAudioSync move exactly the manifest's blobs between two
stores (uploads, downloads, and blobs neither side has surfaced as
'unavailable', never dropped); buildManifest hashes a folder into
AudioFileStats for seeding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
@rulingAnts rulingAnts changed the title dekereke_core: pure Dart core package (backlog #1) dekereke_core + phone task mode (backlog #1 & #2) + P0 test kit Jul 2, 2026
claude added 4 commits July 2, 2026 21:23
… relay (backlog #3)

Cloudflare Worker + D1, engine-only per D5 (no R2 binding, no user
blobs, nothing that scales with users' data). Endpoints per the
flextext patterns: owner bootstrap (idempotent for the same client-
minted install), one-time invite mint/claim (atomic NULL-guarded
UPDATE; same-install retry idempotent; wrong secret indistinguishable
from missing), owner approval step, self status polling, and the
desired/reported two-lane relay with CAS on rev columns (409 returns
the current rev). Secrets stored only as SHA-256.

Strictly additive D1 migrations (0001_init.sql). 16 integration tests
run against real workerd + D1 via Miniflare; new worker-engine CI job
(npm ci, typecheck, test). Deploy stays manual/maintainer-only —
wrangler.toml documents the one-time D1 setup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
… check (backlog #4)

apps/companion: Flutter for Windows (D6), sharing dekereke_core with the
phone app. The Health check area is functional already — open a Dekereke
.xml and the core's health rules run against it, picking up the adjacent
-DkUserSettings.xml and audio folder when present. History (P1) and Sync
(P2) are plain-language placeholder shells (no VCS vocabulary).

Dedicated path-filtered workflow (companion.yml): analyze+test on
ubuntu, then a windows-runner release build uploaded as the
dekereke-companion-windows artifact — download, unzip, run
dekereke_companion.exe. Path filters keep the main CI fast.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
… desktop build

The repo-wide 'flutter analyze' broke whenever a sub-project (first
packages/dekereke_core, now apps/companion) wasn't resolved in the app
job — every package/app has its own job, so the app job now analyzes
only lib/ and test/. The windows-2025 runner image cannot locate Visual
Studio for Flutter's CMake generator, so the desktop build pins
windows-2022.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
@rulingAnts rulingAnts changed the title dekereke_core + phone task mode (backlog #1 & #2) + P0 test kit Companion Suite: entire cloud backlog (#1–#4) — core, phone task mode, engine Worker, desktop scaffold, P0 kit Jul 2, 2026
claude added 6 commits July 2, 2026 22:35
Android Studio + AVD on the Mac host (never inside the Parallels VM —
nested virtualization), drag-and-drop APK + sample.dektask install, host
microphone setup for recording tests, appetize.io as the zero-install
alternative, and the checklist of what to verify in both modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
20 tick-boxes across plain mode, task mode, audio spec, the Dekereke P0
kit, the Companion artifact, and the phone→Dekereke round trips —
exactly the part of the project machines can't verify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…em, panel-reviewed

Consent covers a SCOPE (one ceremony per speaker × imported wordlist),
never per recording; every collected item stamps receiptId + content
hash. Adversarial review (field-usability / IRB-audit / implementation-
fit) folded in: scope-change triggers close the plain-mode and task-
re-issue holes; continuation prompts get their own short researcher-
recorded content; receipts are tamper-evident (canonical-JSON content
hash + per-device hash chain + audio hashes + playback evidence);
'covering receipt' precisely defined for validateResult; canonical-JSON
vs .txt rendering rule; withdrawal UI route added (shipped app has none
post-assent); format carriers and version-bump story specified.

Blocked only on Seth's §6 decisions (Q-A..Q-D).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…r task

Q-A resolved with a better model than time-based continuation: one
ceremony per task package; researcher-configurable re-consent button
(default enabled) for voice/person changes; multiple per-voice
ceremonies per task distinguished by the speaker-name field (Q-C:
optional by default); task-builder advisory that every person involved
gives their own permission; scope precision lives in the researcher-
supplied statement. Q-B: no IP/location in v1. Q-D: withdrawal exports
items flagged. Time-based prompts demoted to optional extras, default
none. Design is now fully decided; implementation queue updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
…ts, coverage validation (D11)

ConsentConfig schematizes task.json's consent block (two axes: ask
text/audio × confirm yesno/record/signature; scope statement; re-consent
button default-on; optional time-based continuation with its own short
researcher content; speaker-name requirement) with plain-language
coherence validation; legacy free-form blocks parse as disabled with
everything preserved.

ConsentReceipt is sealed with a canonical-JSON content hash (key-order
independent), supports the per-device prevReceiptSha256 chain,
refersTo+hash links for continuation/withdrawal, playback evidence, and
a deterministic human .txt rendering footered with the JSON hash —
editing a receipt after the fact is detected on decode and orphans every
stamp.

Format carriers: .dektask gains a consent/ member (decode rejects tasks
referencing unbundled prompt audio); .dekresult bundles receipts
(json+txt) + assent audio, and values/recordings entries carry optional
receiptId/receiptSha256/collectedAt. validateResult enforces the
covering-receipt rules (kind, hash, chain, scope, withdrawal ordering)
only when the task configures consent — 19 new tests, 193 core total;
all 57 app tests stay green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LebVeDXrBBU46RpZ2kQET5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants