Feat: stage 0 core spec - #63
Merged
Merged
Conversation
Four modules under forbid(unsafe), no platform dependencies, 19 tests. search: the fold applied to index and term alike, which is what 2.x gets wrong in both directions for Straße, encyclopædia and Łódź. formats: payload, presence or never. Fixes the type that hangs, the ten megabytes of icon Finder offers for a nine-byte file, PNG over TIFF, the legacy twins, and password detection by type rather than content. hash: whole content up to 256 KB, sixteen spread blocks above it. Two screenshots sharing half an image no longer collide. paste: the clipboard is written before anything touches focus, unknown focus still pastes, a successful send is never retried, races get two retries and a denied send none, and Secure Input warns without aborting. Also fixes three CI guards that failed on their own first run: the SPDX identifier in the licence allow-list, wildcard paths for workspace-internal dependencies, and the synthetic merge commit GitHub checks out on a PR.
The UTI tables had landed inside cp-core, which ties the core to macOS: Windows speaks CF_UNICODETEXT and CF_HDROP, not public.utf8-plain-text. cp-core now holds a Catalog of data only, with the rules written once on top of it. cp-mac supplies the macOS table; cp-win will supply its own without touching the core. The core's tests use invented type names to keep that boundary honest, while cp-mac's assert against the real type lists measured from Safari, Finder and Preview. Also drops three unsafe blocks the -sys crate did not need: objc2 exposes the whole pasteboard surface as safe, so macOS needs none so far.
…esting watch: the sequence-number receipt that invariant 2 asks for. A jump of three means two copies happened and were lost, and they get counted rather than ignored. Our own write is discarded by the exact count that produced it, so a real copy right after it is no longer swallowed. Five proptest properties cover generated tick sequences, the central one being conservation: every emitted event matches a step forward or a session restart, and whatever advanced without being emitted is counted as missed. The regression seed for the shrunk counterexample is checked in, so a backwards counter is retried before any generated case. cargo-mutants went from 11 survivors to none over 59 mutants, and the survivors taught three things: the backwards-counter branch was dead code already covered by max(0); the sampling hash cannot see a change that falls between blocks, which is now a documented test rather than a surprise; and wasteful now explicitly beats wanted when a type is in both lists. Threshold coherence moved to compile-time assertions. CI gains a mutants job so the suite cannot quietly stop proving anything.
The item model now distinguishes five states of a format instead of present or absent: inline, blob, too big with its size recorded, announced but never asked for, and asked for but empty. The last one is not theoretical: linkpresentation.metadata and fndf both do exactly that. capture applies the catalogue against the live pasteboard. Concealed content is decided before a single byte is read, legacy twins collapse so the item is not stored twice, and the costlier image representation is recorded without ever being requested, so the source application does not have to materialise it either. System invariants cannot live in cargo test: NSPasteboard requires the main thread and the test runner does not guarantee it. They move to an executable harness, cargo run -p cp-mac --example probe, which is wired into CI and currently passes all three checks against the real pasteboard.
…policy The FTS index is fed from search_text, which stores the same fold applied to the query. Both sides match because it is the same function, so the four cases 2.x gets wrong now resolve: strasse finds Straße, encyclopaedia finds encyclopædia, lodz finds Łódź. And the direction nobody tests, typing the special character to find it, works too. An item keeps a row per offered format, including the ones carrying no bytes. Broken items are marked rather than deleted, survive until their deadline, do not restart their clock when marked again, and are never purged while pinned. cargo-mutants on cp-store: 25 mutants, none survive on the first pass. The CI job now covers both crates.
Three probes that are not the same thing: CGPreflightPostEventAccess is the one this app needs, AXIsProcessTrusted only opens the menu fallback, and IsSecureEventInputEnabled is read to warn and never to abort. can_paste() depends on the first alone. Keycode resolution asks which key produces "v" with the Command bit set, which covers every layout in one formulation. Layouts can be queried by identifier without activating them, so checking Dvorak no longer means changing anyone's keyboard. Measured: every layout resolves to 0x09 except plain Dvorak, which needs 0x2F. The 2.x constant types Cmd+K there. This is the first unsafe in the project. It stays inside cp-mac-sys, one operation per block, each with its SAFETY note, enforced by clippy. CLA moves to version 1.2, adopting the successor clause: the rights granted travel with the project if it is ever assigned. Signatures already given stay valid, since a later version only governs later contributions.
…stem The concealed-content invariant was only tested against in-memory lists. The harness now writes what a password manager writes, the secret plus its marker, and asserts nothing is captured: ConcealedType, the transient type, and three legacy markers from 1Password, KeeWeb and Flycut. Plus the way back, so exclusion does not quietly become "nothing is ever stored". The watcher can finally poll from its own thread. Reading the counter alone materialises no representation, so it stays clear of the file-promise path where NSPasteboard's documented concurrency bug lives; everything else still requires the main thread, enforced at compile time. Twelve properties added where only examples existed: canonical must be idempotent or deduplication silently breaks, retries are bounded, nothing is retried after a successful send, and placement is monotonic in size. Coverage measured with the harness included, because without it every platform module reads as zero: 96.92% lines, 98.45% functions. A CI job holds the floor at 90% and includes the harness, so the difficult half cannot be deleted to make the number look better.
The schema had been designed from the case file rather than from the DDL of something that has been running for years. Reading it found five things that were about to be lost and one bug about to be repeated. auto_vacuum is ignored silently on a database that already has tables, so 2.x ran for months believing it compacted when it did not. Pragmas now run before any CREATE TABLE, with a test that fails if the mode is not INCREMENTAL. Recovered: modified_at as the list order, so copying something again lifts it instead of burying it; label and app_source in the search index, so a label or a source application can be searched for; a colour index; the thumbnail column; and the composite index the type tab actually queries. Added for the sync that will come, cheap now and expensive later: a monotonic updated_at, distinct from modified_at because pinning an old item should not lift it but must still sync, and deleted_at tombstones, without which a first sync resurrects everything deleted elsewhere. The budget caught the regression this caused and the fix was not what it looked like: the corpus was artificial, repeating one word in every row. With realistic data the correct ordering costs 2.7ms, not 34.
Hiding the panel with orderOut leaves CopyPaste active, so asking who is frontmost at that moment reads ourselves and the paste returns to the panel. That reason is real and is inherited literally; the observer 2.x uses to work around it is not needed, because the watcher is already looking. The logic is "the last one in front that was not us" and it lives in cp-core with eight tests, while the platform crate only reports who is frontmost. Two improvements fall out of writing it that way: the pid is kept alongside the bundle, which closes finding 24 because identifying a target by bundle alone picks arbitrarily between two windows of the same application, and an application that quits stops being a destination instead of staying pinned. The timer in kCFRunLoopCommonModes turns out not to be an invariant at all. It stops firing during menu tracking because it shares a run loop with the interface; a watcher on its own thread has no such run loop to be blocked by. It is a patch, and it goes away with the architecture that caused it.
…ttery The paste sequence applies the core state machine against the real system: the keycode is resolved once per layout rather than on every paste, physical modifiers are read from the event source because a global hotkey never reaches the application's keyboard, and the send carries the left-command device bit with 9ms between down and up, without which Chromium deduplicates and pasting "sometimes fails". The harness becomes a battery of 26 numbered cases across six groups: capture and formats, privacy, watcher, keyboard, permissions, destination and paste. F4 is the one that was missing, a real round trip through TextEdit, verified without accessibility by pasting, selecting all, copying and checking what comes back. When another application holds the foreground and open cannot activate the target, the case is skipped with its reason instead of failing: that is the cooperative activation this project already measured, not a core defect.
cargo-deny had been failing in CI for three pushes: xxhash-rust is BSL-1.0 and the licence was never declared. It fails locally too, so the tool was right and the mistake was running it once and treating the result as permanent. The paster cached its keycode at construction, so changing keyboard layout with the application running kept the stale one, which in Dvorak types a different letter. Resolving it costs 458ns, so the cache bought nothing and carried the bug. Case D5 now checks the paster agrees with the system.
The core knew three kinds; 2.x distinguishes thirteen, and its tab filter lives off that. Losing it would have removed a whole view of the interface without anyone noticing until it was missed. Recovered the six 2.x actually recognised: email, colour in hex, rgb and hsl, ip, uuid, phone and json. Plus the five it declared and never assigned: link, audio, video, image by extension, and folder. Added code, which 2.x does not detect, with a deliberately conservative heuristic and a test that ordinary prose never trips it. No regex: these are hand-written parsers, because this runs on every copy. Family and Kind are now separate things. Family is what the format tells you -- text, image, files -- and Kind is the fine class the content tells you. They were the same type before, which is why the fine classification had nowhere to live.
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.
No description provided.