@wirecat/cli-base 0.51.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/dev/lint.js +2 -2
- package/dist/dev/main.js +21 -3
- package/docs/standards/README.md +3 -2
- package/docs/standards/architecture.md +18 -0
- package/docs/standards/data-model.md +185 -0
- package/docs/standards/security.md +16 -1
- package/docs/standards/store.md +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,25 @@ Versions before 0.8.0 are in the [git tags](https://github.com/WireCatLabs/cli-b
|
|
|
8
8
|
Every entry says what changed as a caller sees it, why, and what to watch for — the rules are
|
|
9
9
|
[`docs/dev/CONVENTIONS.md`](docs/dev/CONVENTIONS.md#the-changelog).
|
|
10
10
|
|
|
11
|
+
## 0.53.0 — 25.10.2026
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **A rule for the provider registry** (`WC-ARCH-21`, [Architecture](docs/standards/architecture.md)): a CLI registers its provider in the store on every run, and a row whose provider no one registered is skipped, never searched as messages. It matches cli-core's stored registry. The Data model, Store and Security pages also list what the approved Wave 3 design adds and is not built yet: files, calendars, contacts by address, one link log and review list, a CLI's own SQLite file, and the sign-in module. The Data model page now names the attendee table `calendar_entry_participants`, lists `meeting_participants` among the known gaps, and names cli-core's schema test as the check of `WC-DATA-2` to `WC-DATA-4`.
|
|
16
|
+
|
|
17
|
+
### Changed — may break callers
|
|
18
|
+
|
|
19
|
+
- **Every handwritten list needs a query declaration.** The command lint checks lists that lack answer metadata too. Release this with the matching cli-core list-query changes and consumer updates.
|
|
20
|
+
|
|
21
|
+
## 0.52.0 — 25.10.2026
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **`cli-dev audit --groups <file>`** compares the CLIs by the parity groups in that file instead of `PARITY_GROUPS`. The file is a JSON list in the shape `defineParityGroups` takes (`name`, `members`, `covers`, `differences`) and is checked the same way; a bad file stops the run with exit 2 and names the file. A CLI outside WireCatLabs keeps this file in its own repository and lists itself next to our CLIs, with no pull request here. `--differences` still adds to the groups in use, so with both flags it adds to the file's groups. Without the flag nothing changes.
|
|
26
|
+
- **`cli-dev <command> --help`** prints the usage, as `cli-dev --help` does, instead of failing on an unknown option.
|
|
27
|
+
|
|
28
|
+
- **A data-model page in the standards** (`WC-DATA-1` to `WC-DATA-7`, [Data model](docs/standards/data-model.md)). It says which records cli-core owns and which stay private to a CLI, keeps a source's records (a calendar entry, a Drive file, a mail contact) apart from the owner's entities (an event, a document, a person), puts every provider id in `external_id`, lists the standard field names, and makes every link record how it was made, with no link when two candidates tie. Nothing changes in code; older tables follow the rules when they next change.
|
|
29
|
+
|
|
11
30
|
## 0.51.1 — 24.10.2026
|
|
12
31
|
|
|
13
32
|
### Fixed
|
package/dist/dev/lint.js
CHANGED
|
@@ -342,10 +342,10 @@ const CHECKS = {
|
|
|
342
342
|
.map(({ target, option }) => ({ target, message: `${longName(option.flags)} has no role in the vocabulary` }));
|
|
343
343
|
},
|
|
344
344
|
"list-without-query": (document) => handwrittenLeaves(document)
|
|
345
|
-
.filter((command) => command.name === "list" && command.
|
|
345
|
+
.filter((command) => command.name === "list" && command.query === undefined)
|
|
346
346
|
.map((command) => ({
|
|
347
347
|
target: label(command),
|
|
348
|
-
message:
|
|
348
|
+
message: "list declares no query",
|
|
349
349
|
})),
|
|
350
350
|
"query-example": (document) => walk(document.commands).flatMap((command) => command.query
|
|
351
351
|
? exampleProblems(command.query).map(({ field, problem }) => ({
|
package/dist/dev/main.js
CHANGED
|
@@ -14,7 +14,7 @@ import { changelogProblems, docsProblems, JOURNAL_IDS, markdownFiles, packageVer
|
|
|
14
14
|
import { AUDIT_BLOCKS, defineParityGroups, PARITY_GROUPS, REQUIRED_ROOTS } from "../standards/audit.js";
|
|
15
15
|
import { defineReasons } from "../standards/errors.js";
|
|
16
16
|
import { baselineProblems, baselineSchema, LINT_RULES, OVERRIDE_BUDGET, OVERRIDE_RULE_REVIEW, overridesProblems, overridesSchema, rulesForReview, } from "../standards/lint.js";
|
|
17
|
-
import { issuesOf } from "../standards/registry.js";
|
|
17
|
+
import { issuesOf, RegistryError } from "../standards/registry.js";
|
|
18
18
|
import { VOCABULARY } from "../standards/vocabulary.js";
|
|
19
19
|
import { auditClis, scorecardTable, snakeKeys } from "./audit.js";
|
|
20
20
|
import { readCapabilities } from "./capabilities.js";
|
|
@@ -49,14 +49,17 @@ const USAGE = `usage: cli-dev <command> [options]
|
|
|
49
49
|
lint/baseline/<rule>.json to today's violations. --schemas
|
|
50
50
|
is a JSON object of the CLI's own JSON Schemas by name
|
|
51
51
|
audit (--cli <bin> | --commands <file>)... [--schemas <cli>=<file>]... [--reasons <cli>=<file>]...
|
|
52
|
-
[--lint <cli>=<dir>]... [--capabilities <file>] [--complete] [--
|
|
52
|
+
[--lint <cli>=<dir>]... [--capabilities <file>] [--complete] [--groups <file>] [--differences <file>]
|
|
53
|
+
[--baseline <dir>] [--for <cli>] [--json] [--write-baseline]
|
|
53
54
|
several CLIs against every lint rule and against each other:
|
|
54
55
|
required roots, same path same contract, same flag same
|
|
55
56
|
meaning, same entity same schema, same failure same reason.
|
|
56
57
|
Prints the scorecard; fails on a new finding (one naming
|
|
57
58
|
--for's CLI, when given). --lint judges that CLI's lint
|
|
58
59
|
rules by its own lint/ folder; the rest by --baseline
|
|
59
|
-
(audit/baseline)
|
|
60
|
+
(audit/baseline). --groups replaces PARITY_GROUPS with the
|
|
61
|
+
JSON list of parity groups in that file; --differences adds
|
|
62
|
+
to the groups in use`;
|
|
60
63
|
const load = async (cwd, path) => (await import(pathToFileURL(isAbsolute(path) ? path : join(cwd, path)).href));
|
|
61
64
|
const report = (io, problems, ok) => {
|
|
62
65
|
for (const problem of problems)
|
|
@@ -335,6 +338,7 @@ const auditRun = (args, io) => {
|
|
|
335
338
|
capabilities: { type: "string" },
|
|
336
339
|
complete: { type: "boolean", default: false },
|
|
337
340
|
differences: { type: "string" },
|
|
341
|
+
groups: { type: "string" },
|
|
338
342
|
reasons: { type: "string", multiple: true },
|
|
339
343
|
lint: { type: "string", multiple: true },
|
|
340
344
|
baseline: { type: "string", default: "audit/baseline" },
|
|
@@ -445,6 +449,16 @@ const auditRun = (args, io) => {
|
|
|
445
449
|
const baselineDir = at(values.baseline);
|
|
446
450
|
const baseline = readBaseline(baselineDir, values.baseline, problems);
|
|
447
451
|
let groups = PARITY_GROUPS;
|
|
452
|
+
if (values.groups) {
|
|
453
|
+
try {
|
|
454
|
+
groups = defineParityGroups(readJson(at(values.groups)));
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
if (error instanceof RegistryError)
|
|
458
|
+
throw new Error(`${values.groups}: ${error.problems.join("; ")}`);
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
448
462
|
if (values.differences) {
|
|
449
463
|
const schema = v.array(v.strictObject({
|
|
450
464
|
group: v.string(),
|
|
@@ -508,6 +522,10 @@ const auditRun = (args, io) => {
|
|
|
508
522
|
};
|
|
509
523
|
export const main = async (argv, io) => {
|
|
510
524
|
const [command, ...args] = argv;
|
|
525
|
+
if (args.includes("--help")) {
|
|
526
|
+
io.out(USAGE);
|
|
527
|
+
return 0;
|
|
528
|
+
}
|
|
511
529
|
switch (command) {
|
|
512
530
|
case "slow-tests": {
|
|
513
531
|
const [file = "coverage/tests.json", count = "20"] = args;
|
package/docs/standards/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# WireCat CLI standards
|
|
2
2
|
|
|
3
3
|
This handbook holds the rules every WireCat CLI follows: tools, layout, commands, output, MCP, accounts,
|
|
4
|
-
permissions, architecture, time, tests, security, documents, releases and CI. Read this page first. Then read the page
|
|
4
|
+
permissions, architecture, the data model, time, tests, security, documents, releases and CI. Read this page first. Then read the page
|
|
5
5
|
for the area you are about to change.
|
|
6
6
|
|
|
7
7
|
It ships in the `@wirecat/cli-base` package. A CLI that depends on cli-base has it at
|
|
@@ -32,9 +32,10 @@ rules it keeps. A new CLI is ready for its first release when every check under
|
|
|
32
32
|
| [MCP](mcp.md) | `MCP` | 13 | The MCP tools, how they come from commands, what each permission shows |
|
|
33
33
|
| [Accounts](accounts.md) | `ACCT` | 11 | The explicit profile, reads across accounts, scope flags, linked identities |
|
|
34
34
|
| [Permissions](permissions.md) | `PERM` | 21 | Levels, keys, defaults, hard limits, the write guard and its journal |
|
|
35
|
-
| [Architecture](architecture.md) | `ARCH` |
|
|
35
|
+
| [Architecture](architecture.md) | `ARCH` | 17 | Layers, dependency direction, vendor types, what goes in which package |
|
|
36
36
|
| [Lifecycle](lifecycle.md) | `LIFE` | 13 | Opening late, who closes, one-shot exits, long-running commands, the `serve` relay, resumable syncs |
|
|
37
37
|
| [Store](store.md) | `STORE` | 14 | Store areas, account scope, migrations, test isolation |
|
|
38
|
+
| [Data model](data-model.md) | `DATA` | 7 | Core entities, entities and source records, `external_id`, field names, links and duplicates |
|
|
38
39
|
| [Testing](testing.md) | `TEST` | 17 | Sandbox, no network, no real waits, leak tests, coverage, contracts, test matrix, parity |
|
|
39
40
|
| [Security](security.md) | `SECR` | 20 | Secrets, logs, keyring, argv, error text, untrusted text, scanners, workflow hardening, reporting |
|
|
40
41
|
| [Documents](docs.md) | `DOC` | 20 | User pages and developer pages, generated pages, current facts, changelog |
|
|
@@ -204,6 +204,24 @@ at start-up and the `serve` relay are in [Lifecycle](lifecycle.md); the store's
|
|
|
204
204
|
- **Exceptions:** none.
|
|
205
205
|
- **Applies to:** cli-base. **Since:** cli-base 0.41.0. **Rulings:** NEED-15.
|
|
206
206
|
|
|
207
|
+
### WC-ARCH-21 · A CLI registers its provider in the store; an unknown provider's rows are skipped, never guessed
|
|
208
|
+
|
|
209
|
+
Every run, a CLI writes its provider's entry — `{ name, kind, aliases, command }` — with `registerProvider` from
|
|
210
|
+
`@wirecat/cli-core/store`. cli-core reads its built-in providers plus the stored entries when the store opens, so
|
|
211
|
+
every other CLI knows the new provider's kind without a cli-core release. A row whose provider no entry names is left
|
|
212
|
+
out of search and listed under `skipped`, with the hint to run that CLI once.
|
|
213
|
+
|
|
214
|
+
- **Why:** a new source joins search and memo without editing cli-core, and a provider nobody described is never read
|
|
215
|
+
as something it is not (until this rule, an unlisted provider was searched as a messenger).
|
|
216
|
+
- **Enforced by:** `cli-core: src/store/sqlite/providers.test.ts`.
|
|
217
|
+
- **Example:** cal registers `{ name: "google-calendar", kind: "events", aliases: ["cal"], command: "cal" }` each
|
|
218
|
+
time it starts. On a machine where cal has never run, a search across accounts lists cal's accounts under
|
|
219
|
+
`skipped` instead of reading their rows as messages.
|
|
220
|
+
- **Exceptions:** the built-in providers of cli-core need no entry. Two entries that disagree on a kind are an error
|
|
221
|
+
naming both.
|
|
222
|
+
- **Applies to:** cli-core and every CLI with a provider of its own. **Since:** cli-base 0.53.1. **Rulings:** none
|
|
223
|
+
(Wave 3 requirements 3.2).
|
|
224
|
+
|
|
207
225
|
## Sources
|
|
208
226
|
|
|
209
227
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Data model
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
How records are modelled in the shared store: which entities cli-core owns, how a source's records relate to the
|
|
6
|
+
owner's real things, where provider ids go, what fields are called, and how records are linked without making
|
|
7
|
+
duplicates.
|
|
8
|
+
|
|
9
|
+
Rule ids on this page are `WC-DATA-<n>`. How to read a rule, how ids work and which rule wins are in the
|
|
10
|
+
[index](README.md).
|
|
11
|
+
|
|
12
|
+
Related pages: the store itself, its areas and migrations are in [Store](store.md); UTC times and date-only values are
|
|
13
|
+
in [Time](time.md); secrets that must never reach the store are in [Security](security.md).
|
|
14
|
+
|
|
15
|
+
### Words used here
|
|
16
|
+
|
|
17
|
+
- **Entity:** the real thing the owner means — a person, an event, a document. It carries no provider id. Tables:
|
|
18
|
+
`persons`, `events`, `event_series`, `documents`.
|
|
19
|
+
- **Source record:** one source's copy of an entity — a mail contact, a Telegram user, a calendar entry, a Zoom
|
|
20
|
+
meeting, a Drive file, a local file. It carries the account or provider it came from and that provider's id.
|
|
21
|
+
Tables: `identities` (contacts), `calendar_entries`, `meetings`, `files`, `emails`, `messages`.
|
|
22
|
+
- **Link:** the reference from a source record to its entity, with how it was made.
|
|
23
|
+
- **Core entity:** an entity or source record whose table cli-core owns, because more than one tool may need it.
|
|
24
|
+
|
|
25
|
+
### Who follows these rules today
|
|
26
|
+
|
|
27
|
+
These rules are new. Older tables break some of them; a change to one of those tables follows the rule, and nothing
|
|
28
|
+
is rewritten only to follow it:
|
|
29
|
+
|
|
30
|
+
- Drive files are `documents` rows under a folder account, with Google's facts in `front_matter` JSON and the folder
|
|
31
|
+
tree in `metadata.catalog` (WC-DATA-3, WC-DATA-5).
|
|
32
|
+
- Drive owners are linked by Google's user id under provider `google-drive`, mail senders by address under `email`,
|
|
33
|
+
and nothing joins the two (WC-DATA-6).
|
|
34
|
+
- `events` rows made from Zoom meetings have no source record of their own besides the meeting (WC-DATA-2 holds:
|
|
35
|
+
`meetings` is that record).
|
|
36
|
+
- Local folders are accounts with provider `folder` (an Obsidian vault and a plain folder look the same), and Drive's
|
|
37
|
+
stand-in account uses it too (WC-DATA-3).
|
|
38
|
+
- Contacts live in `identities`, with no address column: an address is only the `external_id` of provider `email`
|
|
39
|
+
(WC-DATA-6).
|
|
40
|
+
- `documents` keeps its source columns (`account_id`, `external_id`, `url`, `mime`, `front_matter`…) next to the
|
|
41
|
+
entity's own fields (WC-DATA-2).
|
|
42
|
+
- `meeting_participants` is unique on the meeting and the contact; its `external_id` is in no unique key (WC-DATA-3).
|
|
43
|
+
|
|
44
|
+
## Rules
|
|
45
|
+
|
|
46
|
+
### WC-DATA-1 · A record more than one tool needs is a core entity in cli-core; a CLI's own tables stay private
|
|
47
|
+
|
|
48
|
+
A core entity's table, model, store area and migration live in cli-core, and any CLI reads it through cli-core
|
|
49
|
+
(memo is the general reader). A CLI may keep tables of its own — checkpoints, caches, provider bookkeeping — and no
|
|
50
|
+
other CLI reads them. Transient state is never a core entity.
|
|
51
|
+
|
|
52
|
+
- **Why:** every tool and memo can rely on the same people, events and documents, while a tool's internals stay free
|
|
53
|
+
to change.
|
|
54
|
+
- **Enforced by:** review only.
|
|
55
|
+
- **Example:** calendars, calendar entries, events, persons, contacts, documents and files are core; drive's sync
|
|
56
|
+
checkpoint is drive's own.
|
|
57
|
+
- **Exceptions:** none. A CLI's own tables live in its own SQLite file next to the shared store, with its own
|
|
58
|
+
migrations, and refer to core rows by id only; no other CLI opens that file (NEED-1737). The shared store stays
|
|
59
|
+
cli-core's alone ([WC-STORE-5](store.md)).
|
|
60
|
+
- **Applies to:** cli-core and every CLI. **Since:** cli-base 0.52.0. **Rulings:** NEED-1730, NEED-1737.
|
|
61
|
+
|
|
62
|
+
### WC-DATA-2 · Keep a source's records apart from the owner's entities, and link them
|
|
63
|
+
|
|
64
|
+
What a source returns is stored as a source record; the thing the owner means is an entity; the source record links
|
|
65
|
+
to its entity. One entity may combine source records from several sources and accounts.
|
|
66
|
+
|
|
67
|
+
- **Why:** the same meeting in two calendars and in Zoom is one event to the owner; the same document in Drive and on
|
|
68
|
+
disk is one document.
|
|
69
|
+
- **Enforced by:** `cli-core: src/store/sqlite/schema.test.ts`, for the entity tables only; which table is which kind is review.
|
|
70
|
+
- **Example:** a Google Calendar entry is a `calendar_entries` row with `event_id`; the Zoom recording of that meeting
|
|
71
|
+
is a `meetings` row with the same `event_id`.
|
|
72
|
+
- **Exceptions:** an entity the owner makes has no source record; a source record may wait unlinked until matching
|
|
73
|
+
runs.
|
|
74
|
+
- **Applies to:** cli-core. **Since:** cli-base 0.52.0. **Rulings:** NEED-1731.
|
|
75
|
+
|
|
76
|
+
### WC-DATA-3 · A provider's id goes in `external_id`
|
|
77
|
+
|
|
78
|
+
The id a provider gives a record is stored in `external_id`, unique with `account_id` (or with `provider` where the
|
|
79
|
+
record has no account). Never in a column named after the provider (`google_id`, `file_id`) and never only in
|
|
80
|
+
`metadata`. A second id the provider also uses gets a named column of its own (`ical_uid`).
|
|
81
|
+
|
|
82
|
+
- **Why:** one place to look for every provider's id, and one unique key to update a record by.
|
|
83
|
+
- **Enforced by:** `cli-core: src/store/sqlite/schema.test.ts`.
|
|
84
|
+
- **Example:** a Drive file: `files.external_id = '<Drive file id>'`; a Google calendar entry:
|
|
85
|
+
`calendar_entries.external_id = '<event id>'`, `ical_uid = '<iCalUID>'`.
|
|
86
|
+
- **Exceptions:** a local file has no provider id; its `external_id` is its path relative to its folder root.
|
|
87
|
+
- **Applies to:** cli-core. **Since:** cli-base 0.52.0. **Rulings:** NEED-1732.
|
|
88
|
+
|
|
89
|
+
### WC-DATA-4 · Use the standard field names
|
|
90
|
+
|
|
91
|
+
| Meaning | Name |
|
|
92
|
+
|---|---|
|
|
93
|
+
| Primary key; a reference | `id`; `<entity>_id` (`event_id`, `document_id`) |
|
|
94
|
+
| Account, provider, provider's id | `account_id`, `provider`, `external_id` |
|
|
95
|
+
| Subtype; state | `kind`; `status` |
|
|
96
|
+
| Title of content (document, event, chat); name of a person, file or folder | `title`; `name` |
|
|
97
|
+
| Main text; its search form | `body`; `normalized_text` |
|
|
98
|
+
| Web link at the provider (never one that carries a secret) | `url` |
|
|
99
|
+
| File facts | `mime`, `size` (bytes), `content_hash` (sha-256 hex), `path` |
|
|
100
|
+
| Provider extras, front matter | `metadata` (JSON); front matter as `metadata.front_matter` |
|
|
101
|
+
| Our row's times | `created_at`, `updated_at`, `deleted_at` |
|
|
102
|
+
| The provider's times | `external_created_at`, `external_updated_at` |
|
|
103
|
+
| A span; last sync that returned the record | `starts_at`, `ends_at` (exclusive); `seen_at` |
|
|
104
|
+
| A date without a time | `<name>_date` (`YYYY-MM-DD`) |
|
|
105
|
+
| A yes/no | integer 0/1, an adjective (`all_day`, `starred`) |
|
|
106
|
+
| A link to any of several tables | `<noun>able_type`, `<noun>able_id` (`notable_type`) |
|
|
107
|
+
| History; search queue; folded text for filters | `<table>_revisions`; `<entity>_index_pending`; `<field>_folded` |
|
|
108
|
+
|
|
109
|
+
Times are UTC milliseconds ([Time](time.md)).
|
|
110
|
+
|
|
111
|
+
- **Why:** a reader of any table knows what a field holds, and a new table does not invent a third name for "edited
|
|
112
|
+
at".
|
|
113
|
+
- **Enforced by:** `cli-core: src/store/sqlite/schema.test.ts`.
|
|
114
|
+
- **Example:** `files.external_updated_at`, not `modified_at` or `modifiedAt` in JSON.
|
|
115
|
+
- **Exceptions:** existing columns keep their names until their table changes for another reason.
|
|
116
|
+
- **Applies to:** cli-core. **Since:** cli-base 0.52.0. **Rulings:** NEED-1732.
|
|
117
|
+
|
|
118
|
+
### WC-DATA-5 · A field the store filters, sorts, joins or lists on is a column; the rest is allow-listed JSON
|
|
119
|
+
|
|
120
|
+
- **Why:** columns can be indexed and checked; JSON keeps rare provider fields without a migration each.
|
|
121
|
+
- **Enforced by:** review only.
|
|
122
|
+
- **Example:** a Drive file's `mime`, `size` and `external_updated_at` are columns; its `sourceVersion` is
|
|
123
|
+
`metadata.version`.
|
|
124
|
+
- **Exceptions:** none. `metadata` never holds secrets or a provider's raw payload ([Security](security.md)).
|
|
125
|
+
- **Applies to:** cli-core. **Since:** cli-base 0.52.0. **Rulings:** NEED-1733.
|
|
126
|
+
|
|
127
|
+
### WC-DATA-6 · Every link records how it was made; the owner's link wins; ambiguity makes no link
|
|
128
|
+
|
|
129
|
+
A link from a source record to its entity records its `method` (the rule, or `owner`), `confidence`, `author` and
|
|
130
|
+
time, and every change is logged. A rule never replaces an owner's link. When two candidates match equally well, no
|
|
131
|
+
link is made and the pair goes to the owner's review list. Title or time overlap alone never links records.
|
|
132
|
+
|
|
133
|
+
- **Why:** a wrong automatic merge is worse than a duplicate, and the owner must be able to see and undo it.
|
|
134
|
+
- **Enforced by:** review only.
|
|
135
|
+
- **Example:** `identity_links` and `identity_link_events` (contacts to persons); a Zoom meeting at 11:00 next to a
|
|
136
|
+
10–11 and an 11–12 event links to neither by time alone.
|
|
137
|
+
- **Exceptions:** none.
|
|
138
|
+
- **Applies to:** cli-core. **Since:** cli-base 0.52.0. **Rulings:** NEED-1731.
|
|
139
|
+
|
|
140
|
+
### WC-DATA-7 · A source record the provider stops returning is soft-deleted; its entity and links stay
|
|
141
|
+
|
|
142
|
+
- **Why:** a sync error or a revoked share must not erase what the owner linked, wrote or searched.
|
|
143
|
+
- **Enforced by:** review only.
|
|
144
|
+
- **Example:** a calendar sync whose token expired lists again and sets `deleted_at` on entries it did not see; their
|
|
145
|
+
events and meeting links stay.
|
|
146
|
+
- **Exceptions:** an entity with no live source record and no owner link may be removed by an explicit owner command.
|
|
147
|
+
- **Applies to:** cli-core. **Since:** cli-base 0.52.0. **Rulings:** none.
|
|
148
|
+
|
|
149
|
+
## Planned — not built yet
|
|
150
|
+
|
|
151
|
+
From the Wave 3 requirements, approved by the owner on 2026-10-24 (cli-private
|
|
152
|
+
`plans/2026-10-24-open-cli-core-design.md`, sections 3.3–3.6). Each item moves into the rules in the pull request that
|
|
153
|
+
builds it.
|
|
154
|
+
|
|
155
|
+
- **Files.** A `files` table holds the source records of `documents`: one row per item in one place (a local file, a
|
|
156
|
+
Drive file or folder, later a Notion page). It has `account_id`, `external_id` (a local file's path relative to its
|
|
157
|
+
root), `kind` (`file`, `folder`, `page`), `name`, `path` and `path_folded` from the account root, `parent_id` to the
|
|
158
|
+
folder's own row, `document_id`, the file facts and the provider's times (NEED-1739).
|
|
159
|
+
- **Matching a file to a document:** the same account and `external_id` is the same file; a `content_hash` equal to
|
|
160
|
+
the preferred file of exactly one live document joins that document; more than one candidate goes to the review
|
|
161
|
+
list; otherwise a new document. The document shows its most recently changed file, unless the owner pins one.
|
|
162
|
+
- **Folder accounts by kind.** Provider `obsidian` for a vault (front matter, tags, aliases and wiki links kept as
|
|
163
|
+
Obsidian's own metadata), `local` for a plain folder; Drive files under a real `google-drive` account. memo's vault
|
|
164
|
+
files are documents; `notes` are only the owner's notes on entities (NEED-1738).
|
|
165
|
+
- **Calendars.** `calendars` and `calendar_entries` (source records) feed `events` and `event_series` (entities).
|
|
166
|
+
Attendees are `calendar_entry_participants`; changes to tracked fields go to `calendar_entry_revisions` first. An
|
|
167
|
+
entry joins an event by, in order: the same Google id; the same series slot (`original_starts_at`); the same
|
|
168
|
+
`ical_uid` in another calendar; a Zoom event with the same `join_key` and an overlapping time; else a new event.
|
|
169
|
+
- **History on delete.** An event deleted in Google keeps its past occurrences with `source_deleted_at`; future ones
|
|
170
|
+
become `cancelled`. Nothing is removed without an explicit owner command (NEED-1764).
|
|
171
|
+
- **Contacts by address.** A lower-cased `identities.email` column; contacts with the same address link to one
|
|
172
|
+
person (method `email`) unless the owner said otherwise; two persons with one address go to the review list.
|
|
173
|
+
- **One link log and one review list.** `entity_link_events` logs link changes for events and documents in the shape
|
|
174
|
+
of `identity_link_events`; one review list of ambiguous pairs for every entity, resolved by the owner.
|
|
175
|
+
- **A CLI's own SQLite file**, opened through a cli-core helper ([Store](store.md), NEED-1737).
|
|
176
|
+
- **Later:** renaming `identities` to `contacts`; dropping `documents`' source columns once nothing reads them.
|
|
177
|
+
|
|
178
|
+
## Sources
|
|
179
|
+
|
|
180
|
+
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
|
181
|
+
holds.
|
|
182
|
+
|
|
183
|
+
- [cli-core `ARCHITECTURE.md`, "The store"](https://github.com/WireCatLabs/cli-core/blob/main/docs/dev/ARCHITECTURE.md#the-store)
|
|
184
|
+
- cli-private `plans/2026-10-24-open-cli-core-design.md` (private; the rulings NEED-1730 to NEED-1734 are summarised
|
|
185
|
+
above)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Security
|
|
2
2
|
|
|
3
|
-
<!-- cspell:words zizmor -->
|
|
3
|
+
<!-- cspell:words zizmor PKCE -->
|
|
4
4
|
|
|
5
5
|
## Scope
|
|
6
6
|
|
|
@@ -271,6 +271,21 @@ not carried out. A name that matches is not proof of who someone is.
|
|
|
271
271
|
tells an agent about it is [WC-MCP-11](mcp.md#wc-mcp-11--the-server-tells-the-agent-that-message-text-is-data).
|
|
272
272
|
- **Applies to:** all CLIs. **Since:** cli-base 0.44.0. **Rulings:** none.
|
|
273
273
|
|
|
274
|
+
## Planned — not built yet
|
|
275
|
+
|
|
276
|
+
From the Wave 3 requirements, approved by the owner on 2026-10-24 (sections 3.4 and 3.7). Each item moves into the
|
|
277
|
+
rules in the pull request that builds it.
|
|
278
|
+
|
|
279
|
+
- **One sign-in module** in cli-core, `@wirecat/cli-core/oauth`, with no store and no search: authorize (a loopback
|
|
280
|
+
address on `127.0.0.1` or a relay page, `state`, PKCE S256, a timeout, cancel, a paste fallback), exchange, refresh
|
|
281
|
+
and revoke, and a token keeper with a lock per profile across processes. A CLI declares its provider and may
|
|
282
|
+
replace any step, but keeps the explicit profile, the lock, and tokens that are never printed (NEED-1734).
|
|
283
|
+
- **Keys in one place:** one keyring naming scheme for new entries; today's entries of drive and zm are read as they
|
|
284
|
+
are, so no one signs in again.
|
|
285
|
+
- **Meeting secrets never reach the store:** passcodes, PINs, passwords and access codes from a calendar's conference
|
|
286
|
+
data, and the query string of any join URL. Lines of a description that carry a passcode, password or PIN are
|
|
287
|
+
removed.
|
|
288
|
+
|
|
274
289
|
## Sources
|
|
275
290
|
|
|
276
291
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
package/docs/standards/store.md
CHANGED
|
@@ -185,6 +185,17 @@ messages), or other people's data.
|
|
|
185
185
|
- **Exceptions:** none.
|
|
186
186
|
- **Applies to:** all CLIs that use the store, and cli-core. **Since:** cli-base 0.41.0. **Rulings:** none.
|
|
187
187
|
|
|
188
|
+
## Planned — not built yet
|
|
189
|
+
|
|
190
|
+
From the Wave 3 requirements, approved by the owner on 2026-10-24 (NEED-1737). It moves into the rules in the pull
|
|
191
|
+
request that builds the helper.
|
|
192
|
+
|
|
193
|
+
- **A CLI's own tables live in its own SQLite file**, next to the shared store, with its own migrations. They refer to
|
|
194
|
+
core rows by id only, and no other CLI opens that file. A cli-core helper opens it (the path, the migrations, the
|
|
195
|
+
same driver); it is built when the first CLI needs private tables.
|
|
196
|
+
- **WC-STORE-5 gets a check:** a grit plugin, `no-sqlite-driver`, that refuses a CLI's import of a SQLite driver or
|
|
197
|
+
of `drizzle-orm`. cli-core, which owns the store, turns it off.
|
|
198
|
+
|
|
188
199
|
## Sources
|
|
189
200
|
|
|
190
201
|
These documents explain the rules on this page in more detail. Where one of them disagrees with this page, this page
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-base",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"description": "The parts every command line tool needs: output modes, terminal rendering, error model, exit codes, credentials, clocks",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|