@cairnkeep/cli 2.25.0 → 2.27.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 +64 -0
- package/docs/README.md +6 -0
- package/docs/harness-compatibility.md +5 -2
- package/docs/learning/CURRICULUM-MAP.md +3 -3
- package/docs/learning/FEATURE-GUIDE.md +13 -1
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +2 -2
- package/docs/learning/lessons/L03-first-project.md +1 -1
- package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
- package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
- package/docs/learning/lessons/L21-context-packs.md +1 -1
- package/docs/learning/lessons/L22-native-windows.md +2 -2
- package/docs/learning/lessons/L23-guided-setup.md +1 -1
- package/docs/learning/lessons/L24-okf-exchange.md +1 -1
- package/docs/learning/lessons/L25-playbooks.md +1 -1
- package/docs/learning/lessons/L26-context-intelligence.md +1 -1
- package/docs/learning/lessons/L27-ecosystem-evidence.md +1 -1
- package/docs/learning/lessons/L28-security-assurance.md +5 -1
- package/docs/learning/lessons/L29-team-continuity.md +42 -1
- package/docs/learning/tracks/operator.md +5 -0
- package/docs/learning/video-scripts/V28-security-assurance.md +4 -0
- package/docs/learning/video-scripts/V29-team-continuity.md +29 -1
- package/docs/operating.md +10 -0
- package/docs/privacy-and-data-flow.md +30 -1
- package/docs/provider-data-flow-review.md +48 -0
- package/docs/releasing.md +6 -0
- package/docs/security-assurance.md +11 -0
- package/docs/storage.md +23 -0
- package/docs/team-onboarding.md +230 -0
- package/docs/team-operations.md +127 -0
- package/docs/team-pilot.md +2 -0
- package/docs/team.md +21 -4
- package/examples/eval/bundled-fake.json +1 -1
- package/mcp-memory-server/dist/capability-config.js +12 -1
- package/mcp-memory-server/dist/team-audit.js +41 -0
- package/mcp-memory-server/dist/team-cli.js +81 -30
- package/mcp-memory-server/dist/team-client.js +71 -0
- package/mcp-memory-server/dist/team-errors.js +7 -0
- package/mcp-memory-server/dist/team-files.js +28 -0
- package/mcp-memory-server/dist/team-mcp.js +12 -4
- package/mcp-memory-server/dist/team-pages.js +40 -0
- package/mcp-memory-server/dist/team-store.js +105 -40
- package/mcp-memory-server/package.json +2 -2
- package/package.json +1 -1
- package/scripts/cairn-cli.mjs +6 -1
- package/scripts/completion.sh +16 -3
- package/scripts/lib/team-retention.mjs +1 -1
- package/scripts/team-connect.mjs +366 -0
- package/scripts/test-ci-sharding.sh +6 -1
- package/scripts/test-codeql-cohort.mjs +35 -0
- package/scripts/test-completion.sh +7 -1
- package/scripts/test-package-install.sh +1 -0
- package/scripts/test-phase18-capability-lifecycle.sh +2 -2
- package/scripts/test-release-cache.mjs +16 -0
- package/scripts/test-security-baseline.sh +4 -0
- package/scripts/test-smoke-timings.mjs +19 -0
- package/scripts/test-team-docs.sh +26 -2
- package/scripts/test-team-installed.mjs +62 -0
- package/scripts/test-team-lab-env.mjs +9 -0
- package/scripts/test-team-operations.mjs +31 -0
- package/scripts/test-team-operations.sh +5 -0
- package/scripts/test-team-pilot.mjs +43 -1
- package/scripts/verify-published-release.mjs +11 -2
- package/scripts/verify-security-baseline.mjs +20 -0
- package/scripts/windows-platform.mjs +1 -1
|
@@ -138,6 +138,10 @@ prove a future privileged publish run succeeds.
|
|
|
138
138
|
|
|
139
139
|
## Verification evidence
|
|
140
140
|
|
|
141
|
+
The [domain retrieval data-flow review](provider-data-flow-review.md) records
|
|
142
|
+
the intentional file-derived workspace disclosure and its tested destination,
|
|
143
|
+
credential and content boundaries. Static-analysis alerts are not suppressed.
|
|
144
|
+
|
|
141
145
|
The maintained gates are `npm run security:baseline`, both production
|
|
142
146
|
`npm audit` checks, `scripts/test-security-baseline.sh`, the memory-server
|
|
143
147
|
security-assurance and HTTP-guard smoke tests, native-Windows tests, and the
|
|
@@ -161,6 +165,7 @@ node mcp-memory-server/scripts/smoke-runtime-file-reads.mjs
|
|
|
161
165
|
node mcp-memory-server/scripts/smoke-bounded-input-reads.mjs
|
|
162
166
|
node mcp-memory-server/scripts/smoke-workspace-file-security.mjs
|
|
163
167
|
node mcp-memory-server/scripts/smoke-outbound-security.mjs
|
|
168
|
+
node mcp-memory-server/scripts/smoke-security-fixture-order.mjs
|
|
164
169
|
node scripts/test-runtime-file-security.mjs
|
|
165
170
|
node scripts/test-omp-staged-security.mjs
|
|
166
171
|
node scripts/test-container-health.mjs
|
|
@@ -173,6 +178,12 @@ assert that every historical CodeQL/Scorecard alert is fixed. Triage findings
|
|
|
173
178
|
individually and use a fresh default-branch analysis to confirm alert status;
|
|
174
179
|
never dismiss an uninvestigated finding merely to improve the score.
|
|
175
180
|
|
|
181
|
+
The aggregate imports filesystem-hook fixtures sequentially. Top-level-await
|
|
182
|
+
siblings are not a serial test runner: overlapping imports can restore another
|
|
183
|
+
fixture's hooks and create a false failure before growth injection. The
|
|
184
|
+
aggregate ownership/restoration observer covers this test-isolation contract;
|
|
185
|
+
runtime reader checks and injection assertions remain unchanged.
|
|
186
|
+
|
|
176
187
|
The developer-evidence follow-up also hardens the shared runtime note pre-image
|
|
177
188
|
hasher: it reads from an inspected descriptor under a 64 MiB cap and rejects
|
|
178
189
|
replacement or growth before accepting a hash. Missing targets remain valid
|
package/docs/storage.md
CHANGED
|
@@ -605,6 +605,15 @@ no separate prompt or retrieved-content log.
|
|
|
605
605
|
|
|
606
606
|
## Separate team store (opt-in 2.25 preview)
|
|
607
607
|
|
|
608
|
+
From 2.27.0, [team operations](team-operations.md) keep unarchived stores/snapshots
|
|
609
|
+
at v1. Only explicit offline archive/apply changes this team database to v2,
|
|
610
|
+
retaining the terminal sequence/hash and archive/backup digests. All exported
|
|
611
|
+
segments/backups remain external private files. New snapshots include the
|
|
612
|
+
checkpoint; restore revokes credentials and invalidates paging cursors. The
|
|
613
|
+
cursor signing key is local metadata, not exported as a credential. Ordinary
|
|
614
|
+
memory, trajectories, context packs and their layouts are unchanged. Old server
|
|
615
|
+
binaries cannot reopen the v2 store; retain a pre-archive v1 backup for rollback.
|
|
616
|
+
|
|
608
617
|
[Team continuity](team.md) keeps organization/project records in a separate
|
|
609
618
|
`team.db` under `CAIRN_TEAM_BASE_DIR` (default `~/.cairnkeep/team`). Built-in
|
|
610
619
|
SQLite transactions serialize state and audit across processes; personal
|
|
@@ -620,3 +629,17 @@ pages, WAL or backup copies. Uninstall retains team data and refuses purge
|
|
|
620
629
|
targets overlapping its configured directory. Independently selected
|
|
621
630
|
`--data` stores must be outside purge targets or also configured through
|
|
622
631
|
`CAIRN_TEAM_BASE_DIR`. Keep encrypted backups separately protected.
|
|
632
|
+
|
|
633
|
+
Guided client connection state is separate from the organization database:
|
|
634
|
+
`~/.cairnkeep/team-clients/<sha256-of-canonical-project-path>/` holds a strict
|
|
635
|
+
versioned connection, private recovery receipt/original config snapshots and
|
|
636
|
+
an exclusive sibling `<project-path-hash>.operation.lock`. Keeping the open lock
|
|
637
|
+
outside the archived directory preserves native Windows rename semantics and
|
|
638
|
+
continuous writer exclusion. Records contain token-file paths, not raw tokens.
|
|
639
|
+
Original MCP configs can contain other secrets, so protect these backups.
|
|
640
|
+
Activation is committed only after all selected entries are written. An
|
|
641
|
+
interrupted connect stays inactive; interrupted disconnect can resume without
|
|
642
|
+
overwriting unrelated edits. There is no automatic stale-lock cleanup.
|
|
643
|
+
Disconnect archives state in a unique `.disconnected-*` sibling and retains
|
|
644
|
+
credentials; uninstall/purge refuses to delete team client state/backups.
|
|
645
|
+
See [client diagnostics/recovery](team-onboarding.md#client-diagnosis-and-recovery).
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Team preview: a small, explicit handoff
|
|
2
|
+
|
|
3
|
+
For Cairnkeep 2.25.0 or newer. This is **not production admission**;
|
|
4
|
+
no real-data deployment is authorized by following this guide.
|
|
5
|
+
|
|
6
|
+
## Choose the right workflow
|
|
7
|
+
|
|
8
|
+
For your own context across sessions, folders and harnesses, keep ordinary
|
|
9
|
+
`cairn-memory`. You already have that continuity; you do not need a team store.
|
|
10
|
+
Use the team preview when another person should retrieve **selected, reviewed
|
|
11
|
+
project decisions**. Personal memory stays separate. An overlay upgrade does
|
|
12
|
+
not enroll anyone, start a team service or enable sharing.
|
|
13
|
+
|
|
14
|
+
| Person | Responsibility | Keep separate |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Operator | Private service, memberships, credentials and recovery | Deployment consent |
|
|
17
|
+
| Reader / coding client | Own workload token and explicit project | Human-review token |
|
|
18
|
+
| Contributor | Select/redact and propose exact text with consent | Publication authority |
|
|
19
|
+
| Human reviewer | Inspect actual proposal, approve/reject its digest | Agent workspace |
|
|
20
|
+
|
|
21
|
+
## Operator handoff
|
|
22
|
+
|
|
23
|
+
For an explicitly approved **synthetic** rehearsal, follow the
|
|
24
|
+
[isolated pilot setup](team-pilot.md#1-prepare-a-private-lab), then start its
|
|
25
|
+
foreground loopback service. The tested recipe refuses an existing lab and
|
|
26
|
+
preserves personal MCP settings. Do not use production paths.
|
|
27
|
+
|
|
28
|
+
Give each participant a connection card: verified CLI version/path; loopback
|
|
29
|
+
or authenticated-tunnel URL; organization/project IDs; their individual private
|
|
30
|
+
workload-token file and expiry; disposable workspace; revocation contact.
|
|
31
|
+
For this lab the IDs are `trial-org` and `trial-project`.
|
|
32
|
+
|
|
33
|
+
Transfer tokens privately, never through chat, Git, arguments or recordings.
|
|
34
|
+
Keep the reviewer token outside agent workspaces. Same-OS-owner processes are
|
|
35
|
+
not mutually isolated; use separate accounts/machines when that is required.
|
|
36
|
+
|
|
37
|
+
## Reader quick-check
|
|
38
|
+
|
|
39
|
+
In a separate Bash terminal on Linux/macOS, set the operator's paths and URL:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
TEAM_CAIRN=/absolute/path/to/verified-checkout/bin/cairn
|
|
43
|
+
TEAM_PILOT_DIR=/absolute/private/new-team-pilot
|
|
44
|
+
TEAM_PILOT_URL=http://127.0.0.1:17955
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This block makes one read request. It does not create storage, issue credentials,
|
|
48
|
+
start a service or edit a harness. Expect `[]` before any human publication.
|
|
49
|
+
Stop on connection/authorization failure; never fall back to personal memory.
|
|
50
|
+
|
|
51
|
+
<!-- team-reader-check:start -->
|
|
52
|
+
```sh
|
|
53
|
+
set -e
|
|
54
|
+
: "${TEAM_CAIRN:?Set the verified CLI path}"
|
|
55
|
+
: "${TEAM_PILOT_DIR:?Set the private lab directory}"
|
|
56
|
+
: "${TEAM_PILOT_URL:?Set the loopback or authenticated-tunnel URL}"
|
|
57
|
+
team_pilot_reader() {
|
|
58
|
+
env -i PATH="$PATH" CAIRN_TEAM=1 \
|
|
59
|
+
CAIRN_TEAM_URL="$TEAM_PILOT_URL" CAIRN_TEAM_ORGANIZATION=trial-org \
|
|
60
|
+
CAIRN_TEAM_TOKEN_FILE="$TEAM_PILOT_DIR/reader.token" \
|
|
61
|
+
"$TEAM_CAIRN" team "$@" --project trial-project
|
|
62
|
+
}
|
|
63
|
+
team_pilot_reader list
|
|
64
|
+
```
|
|
65
|
+
<!-- team-reader-check:end -->
|
|
66
|
+
|
|
67
|
+
On native Windows, use equivalent explicit environment settings and
|
|
68
|
+
`cairn team list --project trial-project` from [the reference](team.md).
|
|
69
|
+
|
|
70
|
+
Add **only an additional**, disposable `cairn-team-memory` stdio entry:
|
|
71
|
+
the verified command, arguments `team mcp --project-root WORKSPACE`, and the
|
|
72
|
+
same four `CAIRN_TEAM*` settings above. For a reader, also set
|
|
73
|
+
`CAIRN_TEAM_MCP_TOOL_PROFILE=read-only`. Merge into the isolated harness profile;
|
|
74
|
+
never replace `cairn-memory` or its entire configuration. See
|
|
75
|
+
[connection semantics](team.md#harness-connection).
|
|
76
|
+
Each tool call names `trial-project`; cwd and display names grant no access.
|
|
77
|
+
The MCP profile restricts tools; server membership independently restricts data.
|
|
78
|
+
|
|
79
|
+
<a id="guided-connection-next-release--matching-source-checkout"></a>
|
|
80
|
+
|
|
81
|
+
## Guided connection (2.26 or newer)
|
|
82
|
+
|
|
83
|
+
The original preview and reader card above work on 2.25.0. The guided commands
|
|
84
|
+
below require both the client **and team API** at 2.26.0 or newer. They do not
|
|
85
|
+
change ordinary `cairn setup`, personal memory or the preview's
|
|
86
|
+
production-admission boundary.
|
|
87
|
+
|
|
88
|
+
After the operator supplies a **workload reader** credential, opt in explicitly:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
CAIRN_TEAM=1 cairn team connect --project-root /absolute/project
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The wizard asks for URL, organization/project IDs, a private credential-file
|
|
95
|
+
path and harness checkboxes. Its default tool profile is read-only. It displays
|
|
96
|
+
the planned files and asks before applying. Cancellation returns to the shell.
|
|
97
|
+
For automation, supply every choice; without `--apply` this only plans:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
CAIRN_TEAM=1 cairn team connect --project-root /absolute/project \
|
|
101
|
+
--url http://127.0.0.1:17955 --organization trial-org --project trial-project \
|
|
102
|
+
--token-file /absolute/private/reader.token --harness claude,opencode
|
|
103
|
+
# Repeat the same command with --apply --yes to connect.
|
|
104
|
+
CAIRN_TEAM=1 cairn team doctor --client --project-root /absolute/project
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Never pass a token value. Keep the credential file outside this project's
|
|
108
|
+
source tree and owner-only (POSIX 0600; native Windows private ACL). URLs must
|
|
109
|
+
be loopback HTTP or HTTPS, without credentials, query, fragment or path prefix.
|
|
110
|
+
Use the operator's encrypted authenticated tunnel, not a public unauthenticated
|
|
111
|
+
listener. The command authenticates reader access before changing a harness.
|
|
112
|
+
It refuses human-class credentials: the operator must issue a workload token
|
|
113
|
+
for the agent, keeping human review credentials in the separate human terminal.
|
|
114
|
+
|
|
115
|
+
| Harness choice | Project-local file | Additional entry |
|
|
116
|
+
|----------------|--------------------|------------------|
|
|
117
|
+
| `claude` | `.mcp.json` | `mcpServers.cairn-team-memory` |
|
|
118
|
+
| `opencode` | `opencode.json` | `mcp.cairn-team-memory` |
|
|
119
|
+
| `qwen` | `.qwen/settings.json` | `mcpServers.cairn-team-memory` |
|
|
120
|
+
| `codex` | `.codex/config.toml` | marked `mcp_servers.cairn-team-memory` block |
|
|
121
|
+
|
|
122
|
+
No global harness file is written. Existing unrelated entries are preserved;
|
|
123
|
+
an existing team entry, duplicate JSON keys, JSONC or unsupported inline TOML
|
|
124
|
+
causes refusal rather than replacement. OpenCode `opencode.jsonc` is not edited.
|
|
125
|
+
Pi/OMP/Kimi use the manual bridge recipe; this wizard does not claim automated
|
|
126
|
+
configuration for them. Harness-native trust/enablement prompts still apply.
|
|
127
|
+
Adapter regression uses real SDK stdio calls, not live model/harness UI sessions.
|
|
128
|
+
|
|
129
|
+
The generated entry invokes the verified Node/CLI paths with a private
|
|
130
|
+
`--connection` file. It pins the server project even if the harness starts in
|
|
131
|
+
another folder; each tool call still names that ID. It does not grant server
|
|
132
|
+
membership. Contributors can explicitly select `--access full` to expose
|
|
133
|
+
proposal creation, still subject to existing profile/capability restrictions
|
|
134
|
+
and server authorization. Human review/admin tools never become available.
|
|
135
|
+
Changing connection settings requires disconnect/reconnect; credentials may be
|
|
136
|
+
rotated in place without a harness restart because the bridge rereads the file.
|
|
137
|
+
|
|
138
|
+
### Client diagnosis and recovery
|
|
139
|
+
|
|
140
|
+
Client doctor verifies the active managed entries and private credential file,
|
|
141
|
+
then makes one authenticated `connection-check` request. It reports the current
|
|
142
|
+
subject, credential ID/class/expiry, project and roles, **not** memory text,
|
|
143
|
+
proposals, token values or environment snapshots. The service appends its normal
|
|
144
|
+
value-free audit event. It opens no local administrator store and cannot issue
|
|
145
|
+
credentials, grant membership or repair permissions. Wrong org/project, missing
|
|
146
|
+
reader membership and expired/revoked credentials require the operator; failed
|
|
147
|
+
team access never falls back to personal memory. Check the tunnel/TLS and API
|
|
148
|
+
version on connection failure. Store doctor remains `team doctor --data DIR`.
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
CAIRN_TEAM=1 cairn team disconnect --project-root /absolute/project
|
|
152
|
+
# Inspect the no-write plan, then:
|
|
153
|
+
CAIRN_TEAM=1 cairn team disconnect --project-root /absolute/project --apply --yes
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Disconnect removes only unchanged owned entries. It restores exact original
|
|
157
|
+
bytes when nothing else changed; otherwise it preserves unrelated edits and
|
|
158
|
+
refuses an altered team entry. It keeps the token and private backups. Restart
|
|
159
|
+
existing harness sessions; disconnect is **not revocation**. The operator must
|
|
160
|
+
revoke a credential/remove membership for immediate denial in running sessions.
|
|
161
|
+
|
|
162
|
+
Connection records and original configuration snapshots are private under
|
|
163
|
+
`~/.cairnkeep/team-clients/<canonical-project-path-sha256>/`. Backups may include
|
|
164
|
+
secrets already present in your original MCP config: never commit or publish
|
|
165
|
+
them; use separately encrypted backups as appropriate. No token content is
|
|
166
|
+
copied from its file. Disconnected records are retained in uniquely named
|
|
167
|
+
`.disconnected-*` siblings. Uninstall/purge refuses to delete this retained state.
|
|
168
|
+
|
|
169
|
+
A per-project exclusive `<project-path-hash>.operation.lock` beside the state
|
|
170
|
+
directory serializes Cairnkeep writers and stays held while state is archived,
|
|
171
|
+
including on native Windows. A failed
|
|
172
|
+
multi-file connect keeps its recovery record in `preparing`; the bridge refuses
|
|
173
|
+
startup until **all** entries are written and activation is committed. Run
|
|
174
|
+
disconnect to undo an interrupted operation after checking for configuration
|
|
175
|
+
drift. An interrupted disconnect is likewise resumable. Do not restore a whole
|
|
176
|
+
backup over newer unrelated edits. If the process crashed holding its lock,
|
|
177
|
+
first verify no connection/disconnection process is running, preserve the state
|
|
178
|
+
directory, then manually remove **only that project's** sibling `.operation.lock` and
|
|
179
|
+
retry disconnect. The tool never guesses that a live lock is stale.
|
|
180
|
+
|
|
181
|
+
Configuration directories must be owned/trusted and not writable by other users.
|
|
182
|
+
Each configuration is bounded to 1 MiB and the four-file recovery record to
|
|
183
|
+
16 MiB, including both original/new bytes and base64 overhead.
|
|
184
|
+
Links and unsafe files are rejected. Writers use bounded descriptor-first reads,
|
|
185
|
+
exclusive creation and atomic replacement with a final drift check. This is not
|
|
186
|
+
an OS compare-and-swap against a hostile process running as your own identity;
|
|
187
|
+
stop concurrent editor writes before applying. No root-directory or global
|
|
188
|
+
configuration target, automatic permission changes to existing directories,
|
|
189
|
+
automatic enrollment or background synchronization is introduced.
|
|
190
|
+
|
|
191
|
+
## Contributor and reviewer
|
|
192
|
+
|
|
193
|
+
Contributors get their **own** workload credential and explicit contributor
|
|
194
|
+
membership. Start with one synthetic decision; select/redact exact text and
|
|
195
|
+
confirm its value digest using the [proposal reference](team.md#harness-connection).
|
|
196
|
+
There is no automatic personal-memory import.
|
|
197
|
+
|
|
198
|
+
In the separate human terminal, inspect `proposal-show`, then approve/reject
|
|
199
|
+
the actual stored bytes with their proposal digest. Never hand a human token
|
|
200
|
+
to a model. Automated tests of human-class tokens are not human consent.
|
|
201
|
+
Restart a reader in another folder: the approved decision/provenance must match;
|
|
202
|
+
unrelated-project access and revoked credentials must fail. Follow the
|
|
203
|
+
[pilot checks and cleanup](team-pilot.md#4-exercise-the-actual-review-boundary).
|
|
204
|
+
Native trials require separately authorized providers; no model purchase or
|
|
205
|
+
subscription use is authorized by this guide.
|
|
206
|
+
|
|
207
|
+
## Deployment readiness
|
|
208
|
+
|
|
209
|
+
The [operations guide](team-operations.md) adds paging, capacity diagnostics
|
|
210
|
+
and backup-bound explicit audit archival from 2.27.0. Use matching upgraded
|
|
211
|
+
client/API/admin binaries. Do not infer enrollment or admission from it.
|
|
212
|
+
|
|
213
|
+
Before requesting a real-data pilot, record an owner and evidence for:
|
|
214
|
+
|
|
215
|
+
- **Purpose:** project IDs, participants, data classes, one continuity use case,
|
|
216
|
+
explicit exclusions and a stop date.
|
|
217
|
+
- **Consent:** selected-data approval, independent production security review
|
|
218
|
+
and deployment authorization. Mark absent decisions pending; tests supply none.
|
|
219
|
+
- **Access:** least-privilege roles, individual expiring tokens, separate human
|
|
220
|
+
review, authenticated tunnel/TLS, private storage and trusted host ownership.
|
|
221
|
+
- **Operation:** named operator, revocation/offboarding contact, storage/audit
|
|
222
|
+
capacity, encrypted backup retention and a rehearsed fresh-only restore.
|
|
223
|
+
Restored credentials are revoked; reissue and activation remain explicit.
|
|
224
|
+
- **Acceptance/rollback:** continuity across clients/folders, denied/unapproved
|
|
225
|
+
content hidden, immediate revocation and unchanged personal config hashes.
|
|
226
|
+
Stop the separate service and remove only its additional client entry to
|
|
227
|
+
withdraw access; preserve agreed private evidence.
|
|
228
|
+
|
|
229
|
+
Until these decisions are explicitly approved, stay in the disposable synthetic
|
|
230
|
+
lab. No automatic production transition follows the rehearsal.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Team operations: bounded history without automatic deletion
|
|
2
|
+
|
|
3
|
+
Available from **Cairnkeep 2.27.0**. Use 2.27.0 or newer client/API/admin
|
|
4
|
+
binaries for paging and archival; older releases do not include these commands.
|
|
5
|
+
The team preview remains default off and is not general production admission.
|
|
6
|
+
|
|
7
|
+
## Optional pagination
|
|
8
|
+
|
|
9
|
+
`list`, `history`, `proposals`, `audit` and `search` accept an optional `page`:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{"organization":"demo-org","project":"alpha","operation":"list","page":{"size":20}}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The result is `{ "items": [...], "next_cursor": "..." }`; the final cursor
|
|
16
|
+
is `null`. Without `page`, existing bounded arrays and ordering remain unchanged.
|
|
17
|
+
Sizes are 1–100, or 1–10 for search. The existing team MCP list/search/history
|
|
18
|
+
tools accept the same optional object. No admin/review tool is added to MCP.
|
|
19
|
+
CLI equivalents are `--page-size N --cursor CURSOR`. Repeat the same project,
|
|
20
|
+
operation, filters and page size when continuing.
|
|
21
|
+
|
|
22
|
+
Cursors are authenticated and bound to the credential, project, roles/policy,
|
|
23
|
+
filters and visible result set. Tampering or cross-identity reuse fails;
|
|
24
|
+
changed results cause a conflict requiring a new first page. Every call checks
|
|
25
|
+
current membership, expiry and revocation. Cursor validity is not authorization.
|
|
26
|
+
Audit paging freezes an upper sequence so its own read events do not invalidate
|
|
27
|
+
the traversal. New events are visible in a fresh traversal. Restore invalidates
|
|
28
|
+
old cursors; its new credentials require a fresh traversal too.
|
|
29
|
+
|
|
30
|
+
## Capacity before refusal
|
|
31
|
+
|
|
32
|
+
`cairn team doctor --data DIR` remains local, metadata-only and non-mutating.
|
|
33
|
+
It reports record/audit entries, UTF-8 bytes, ceilings, remaining space and
|
|
34
|
+
`available`, `near-limit` (80%) or `exhausted` status. These are diagnostics,
|
|
35
|
+
not a guarantee that the next write fits. Records remain bounded at 32,768 and
|
|
36
|
+
16 MiB. Ordinary audit admission is 99,000 events/16 MiB, with a separate local
|
|
37
|
+
operator reserve of 1,000 events/256 KiB. Nothing is evicted automatically.
|
|
38
|
+
Archiving audit does not free record capacity.
|
|
39
|
+
|
|
40
|
+
## Offline, exact-digest archival
|
|
41
|
+
|
|
42
|
+
Only the trusted local OS operator can archive. **Stop the team API and bridge
|
|
43
|
+
writers first**, and use the matching new admin binary. Existing older server
|
|
44
|
+
processes cannot be retroactively made schema-aware. Do not archive a store
|
|
45
|
+
while an older service still has it open.
|
|
46
|
+
|
|
47
|
+
Choose private paths outside source trees; ancestors must not be writable by
|
|
48
|
+
other users/groups. Keep the backup and archive on durable protected storage.
|
|
49
|
+
The following paths are placeholders, not production defaults:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
CAIRN_TEAM=1 cairn team doctor --data /absolute/private/team
|
|
53
|
+
CAIRN_TEAM=1 cairn team backup --data /absolute/private/team --read-only \
|
|
54
|
+
--output /absolute/private/backups/pre-archive.json
|
|
55
|
+
CAIRN_TEAM=1 cairn team audit-export --data /absolute/private/team \
|
|
56
|
+
--backup /absolute/private/backups/pre-archive.json \
|
|
57
|
+
--output /absolute/private/backups/audit-segment.json
|
|
58
|
+
CAIRN_TEAM=1 cairn team audit-verify \
|
|
59
|
+
--input /absolute/private/backups/audit-segment.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`--read-only` makes a consistent backup without adding an audit event, including
|
|
63
|
+
when the local reserve is full. Normal backup retains its existing audit event.
|
|
64
|
+
The export verifies its backup against current state, writes exclusively with
|
|
65
|
+
private permissions and file fsync (plus a directory flush on POSIX), and changes
|
|
66
|
+
no database state. Node cannot flush directory handles on Windows; protect and
|
|
67
|
+
retain the exported files on reliable storage before applying compaction.
|
|
68
|
+
It contains audit
|
|
69
|
+
metadata, not memory values or raw bearer tokens. Names and activity are still
|
|
70
|
+
sensitive; an export is not a public report.
|
|
71
|
+
|
|
72
|
+
Use the returned **archive_digest**, not a memory or backup digest:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
# No-write plan:
|
|
76
|
+
CAIRN_TEAM=1 cairn team audit-archive --data /absolute/private/team \
|
|
77
|
+
--input /absolute/private/backups/audit-segment.json \
|
|
78
|
+
--backup /absolute/private/backups/pre-archive.json --confirm ARCHIVE_DIGEST
|
|
79
|
+
# Repeat the exact command with --apply to compact the verified active prefix.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Wrong/tampered/stale backups, archives or confirmation fail. Any intervening
|
|
83
|
+
audited operation makes the plan stale: stop writers and produce a fresh pair.
|
|
84
|
+
Apply atomically records the archive/backup digests and terminal sequence/hash,
|
|
85
|
+
removes the active audit prefix, and appends an archival event. A failed database
|
|
86
|
+
transaction rolls back checkpoint, schema change and removal together. No file
|
|
87
|
+
is removed or overwritten. Keep **all segments and backups** under an explicit
|
|
88
|
+
operator-owned retention policy; no background job or default expiry is added.
|
|
89
|
+
|
|
90
|
+
## Compatibility, recovery and limits of evidence
|
|
91
|
+
|
|
92
|
+
Unarchived stores and snapshots stay version 1. Explicit archival changes only
|
|
93
|
+
this separate team store to version 2; subsequent snapshots carry its checkpoint.
|
|
94
|
+
Older server/admin binaries reject reopening that store. Legacy remote clients
|
|
95
|
+
can still use unchanged non-paged API calls with the new service. To roll back
|
|
96
|
+
the server format, use the retained pre-archive v1 backup in a fresh location;
|
|
97
|
+
never point an older binary at the compacted database. Restore continues to
|
|
98
|
+
revoke every copied credential. Reissue credentials and approve reactivation
|
|
99
|
+
explicitly; never overwrite a live store with a restore.
|
|
100
|
+
|
|
101
|
+
Doctor and snapshot validation check the active audit suffix anchored at its
|
|
102
|
+
checkpoint. They do **not** open external archives or prove full historical
|
|
103
|
+
retention. Verify retained segments with `audit-verify`; pass `--previous FILE`
|
|
104
|
+
to check the adjacent archive link. One successful segment check is not a claim
|
|
105
|
+
that every earlier segment is present. Digests provide integrity, not publisher
|
|
106
|
+
authenticity, independent timestamping or protection against a malicious OS owner.
|
|
107
|
+
Compaction is not secure erasure: backups, archives and SQLite pages may retain
|
|
108
|
+
data. Audit capacity still fails closed if operators take no action.
|
|
109
|
+
|
|
110
|
+
## Synthetic continuity endurance
|
|
111
|
+
|
|
112
|
+
From a matching built source checkout, in a private disposable lab:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
lab=$(mktemp -d)
|
|
116
|
+
CAIRN_TEAM_SOAK=1 node mcp-memory-server/scripts/run-team-continuity-soak.mjs \
|
|
117
|
+
--cycles 120 --duration-ms 1200000 --out "$lab/report.json"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This exercises two actual stdio MCP client processes across working folders,
|
|
121
|
+
separate API process restarts (including an unclean termination), private
|
|
122
|
+
token-file rotation, revoked credentials, audit
|
|
123
|
+
archival and fresh restore. Its human-class reviewer is a **synthetic policy
|
|
124
|
+
fixture**, not actual human consent. No paid model, provider, real memory or
|
|
125
|
+
production configuration is used. Reports contain counts, digests and latency,
|
|
126
|
+
not selected text or bearer credentials. This is transport/authorization
|
|
127
|
+
continuity evidence, not a native-harness UI trial or a model-quality benchmark.
|
package/docs/team-pilot.md
CHANGED
|
@@ -11,6 +11,8 @@ The pilot runs beside existing personal memory: a new private store, expiring
|
|
|
11
11
|
credentials, a separate loopback port and additional isolated client profiles.
|
|
12
12
|
It never imports a personal store or replaces an existing MCP registration.
|
|
13
13
|
Use synthetic project decisions, not employer, personal or customer data.
|
|
14
|
+
For a shorter role-by-role route and reader connection card, see
|
|
15
|
+
[Team onboarding](team-onboarding.md). It does not grant deployment consent.
|
|
14
16
|
|
|
15
17
|
## 1. Prepare a private lab
|
|
16
18
|
|
package/docs/team.md
CHANGED
|
@@ -20,6 +20,8 @@ Sharing changes visibility: inspect and redact the selected text first.
|
|
|
20
20
|
For a trial alongside unchanged personal memory, use the
|
|
21
21
|
[isolated pilot guide](team-pilot.md). It keeps stores, credentials, ports and
|
|
22
22
|
client profiles separate and distinguishes actual human review from rehearsal.
|
|
23
|
+
Start with the [role-based handoff](team-onboarding.md) for operator, coding
|
|
24
|
+
client and human-reviewer tasks, a tested reader check and readiness checklist.
|
|
23
25
|
|
|
24
26
|
## Trust boundary
|
|
25
27
|
|
|
@@ -78,6 +80,15 @@ OIDC/SSO and standards-compliant remote MCP HTTP authorization are deferred.
|
|
|
78
80
|
|
|
79
81
|
## Harness connection
|
|
80
82
|
|
|
83
|
+
Cairnkeep 2.26.0 (both client and team API) adds
|
|
84
|
+
the opt-in [guided connection and recovery workflow](team-onboarding.md#guided-connection-next-release--matching-source-checkout):
|
|
85
|
+
`cairn team connect`, `cairn team doctor --client` and `cairn team disconnect`.
|
|
86
|
+
It adds only a dedicated project-local entry, defaults to read-only, references
|
|
87
|
+
a private token file and pins the server project. No global MCP replacement or
|
|
88
|
+
personal upload occurs. The manual environment recipe below remains supported
|
|
89
|
+
on 2.25.0. The diagnostics API returns only authenticated project/credential
|
|
90
|
+
metadata and records a value-free audit event, not memory text.
|
|
91
|
+
|
|
81
92
|
Set `CAIRN_TEAM_URL`, `CAIRN_TEAM_ORGANIZATION` and `CAIRN_TEAM_TOKEN_FILE` in the
|
|
82
93
|
local harness environment, alongside `CAIRN_TEAM=1`. Tokens are file-only.
|
|
83
94
|
The URL may be loopback HTTP through a tunnel or HTTPS, without embedded
|
|
@@ -203,13 +214,19 @@ store are refused before any uninstall changes; a store selected only through
|
|
|
203
214
|
|
|
204
215
|
## Bounded preview and admission
|
|
205
216
|
|
|
217
|
+
From 2.27.0, optional pagination, capacity diagnostics and explicitly confirmed
|
|
218
|
+
audit archival are available in [team operations](team-operations.md). Upgrade
|
|
219
|
+
the client, API and admin binary before using these operations.
|
|
220
|
+
|
|
206
221
|
Selected values are at most 64 KiB UTF-8; requests 512 KiB; responses 2 MiB;
|
|
207
222
|
snapshots 64 MiB. Records are capped at 32,768 and 16 MiB of stored JSON;
|
|
208
|
-
audit at
|
|
223
|
+
ordinary audit at 99,000 events and 16 MiB, with a separate
|
|
224
|
+
1,000-event/256 KiB OS-operator reserve.
|
|
209
225
|
Limits fail closed rather than discarding records. Back up and review retention
|
|
210
|
-
before approaching them;
|
|
211
|
-
Retrieval is bounded (100 list/history items, ten search results);
|
|
212
|
-
|
|
226
|
+
before approaching them; archival is offline, manual and backup-bound, never
|
|
227
|
+
automatic. Retrieval is bounded (100 list/history items, ten search results);
|
|
228
|
+
optional authenticated paging is available from 2.27.0. HTTP separately limits
|
|
229
|
+
malformed authentication
|
|
213
230
|
per connection address and authenticated requests per subject to 120 per minute;
|
|
214
231
|
invalid token-shaped credentials receive ten attempts per token digest. Each
|
|
215
232
|
budget holds at most 512 identities, and invalid authentication cannot consume
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"identifier":"cairn-offline-fake-v1","package_version":"2.
|
|
1
|
+
{"identifier":"cairn-offline-fake-v1","package_version":"2.27.0","schema_version":1,"task_set_digest":"13e95379f35839593047ec34521a65a8fa3ab92bd3993bca9dc8bf5c4a9ff36f"}
|
|
@@ -222,7 +222,18 @@ async function acquireLock(lockPath) {
|
|
|
222
222
|
catch (error) {
|
|
223
223
|
if (error.code !== "EEXIST")
|
|
224
224
|
throw error;
|
|
225
|
-
|
|
225
|
+
let info;
|
|
226
|
+
try {
|
|
227
|
+
info = await lstat(lockPath);
|
|
228
|
+
}
|
|
229
|
+
catch (inspectionError) {
|
|
230
|
+
// The current writer may release its lock after our mkdir
|
|
231
|
+
// reports contention but before inspection. Only disappearance
|
|
232
|
+
// admits another bounded acquisition attempt, never ownership.
|
|
233
|
+
if (inspectionError.code === 'ENOENT')
|
|
234
|
+
continue;
|
|
235
|
+
throw inspectionError;
|
|
236
|
+
}
|
|
226
237
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
227
238
|
throw new Error("Capability configuration lock is unsafe.");
|
|
228
239
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { TeamError } from './team-errors.js';
|
|
4
|
+
const id = z.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
|
|
5
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
6
|
+
export const teamAuditCheckpointSchema = z.object({ sequence: z.number().int().positive(), digest, archive_digest: digest, backup_digest: digest, previous_archive_digest: digest.nullable() }).strict();
|
|
7
|
+
export const teamAuditRowSchema = z.object({ sequence: z.number().int().positive(), organization: id, project: id.or(z.literal('')), value: z.string().max(4096), digest }).strict();
|
|
8
|
+
export const teamAuditEntrySchema = z.object({ schema_version: z.literal(1), sequence: z.number().int().positive(), organization: id, project: id.or(z.literal('')), subject: id, credential_class: z.enum(['human', 'workload', 'local-os']), operation: z.enum(['project-create', 'member-set', 'credential-revoke', 'credential-issue', 'memory-delete', 'list', 'audit', 'read', 'history', 'search', 'propose', 'proposals', 'proposal-show', 'review', 'snapshot', 'restore', 'connection-check', 'audit-archive']), result: z.enum(['accepted', 'denied', 'conflict', 'invalid']), object_digest: digest.nullable(), previous_digest: digest.nullable(), request_id: z.string().uuid(), at: z.iso.datetime() }).strict();
|
|
9
|
+
const archiveSchema = z.object({ schema_version: z.literal(1), organization: id, backup_digest: digest, previous_checkpoint: teamAuditCheckpointSchema.nullable(), entries: z.array(teamAuditRowSchema).min(1).max(100000), digest }).strict();
|
|
10
|
+
const hash = (text) => createHash('sha256').update(text).digest('hex');
|
|
11
|
+
export function verifyTeamAuditRows(organization, rows, checkpoint) {
|
|
12
|
+
try {
|
|
13
|
+
let sequence = checkpoint?.sequence ?? 0, previous = checkpoint?.digest ?? null, bytes = 0;
|
|
14
|
+
for (const row of rows) {
|
|
15
|
+
const entry = teamAuditEntrySchema.parse(JSON.parse(row.value));
|
|
16
|
+
bytes += Buffer.byteLength(row.value);
|
|
17
|
+
if (row.organization !== organization || row.sequence !== ++sequence || entry.sequence !== sequence || entry.organization !== organization || entry.project !== row.project || entry.previous_digest !== previous || hash(row.value) !== row.digest)
|
|
18
|
+
throw new Error();
|
|
19
|
+
previous = row.digest;
|
|
20
|
+
}
|
|
21
|
+
if (bytes > 16 * 1024 * 1024 + 256 * 1024)
|
|
22
|
+
throw new Error();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new TeamError('integrity');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function verifyTeamAuditArchive(input) {
|
|
29
|
+
try {
|
|
30
|
+
if (Buffer.byteLength(JSON.stringify(input)) > 64 * 1024 * 1024)
|
|
31
|
+
throw new Error();
|
|
32
|
+
const archive = archiveSchema.parse(input), { digest, ...body } = archive;
|
|
33
|
+
if (hash(JSON.stringify(body)) !== digest)
|
|
34
|
+
throw new Error();
|
|
35
|
+
verifyTeamAuditRows(archive.organization, archive.entries, archive.previous_checkpoint);
|
|
36
|
+
return archive;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new TeamError('integrity');
|
|
40
|
+
}
|
|
41
|
+
}
|