@gotcos/glasses-server 6.54.0 → 6.56.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +16 -0
  3. package/bin/control2-foundation.cjs +12 -0
  4. package/bin/task-runtime-check.cjs +65 -0
  5. package/docs/control2-foundation.md +79 -0
  6. package/docs/control2-sandbox.md +47 -0
  7. package/docs/control2-task-runtime.md +68 -0
  8. package/docs/work-reviews.md +25 -0
  9. package/docs/work-task-runtime.md +82 -0
  10. package/package.json +18 -4
  11. package/server/index.ts +8 -0
  12. package/server/lib/control2-draft.ts +118 -0
  13. package/server/lib/control2-foundation.ts +164 -0
  14. package/server/lib/control2-sandbox.ts +192 -0
  15. package/server/lib/lens-gist.ts +110 -16
  16. package/server/lib/task-bridge.ts +85 -0
  17. package/server/lib/task-store.ts +75 -10
  18. package/server/lib/work-activity.ts +106 -0
  19. package/server/lib/work-review-backend.ts +28 -0
  20. package/server/lib/work-review-runtime.ts +203 -0
  21. package/server/lib/work-review-store.ts +102 -0
  22. package/server/routes/control2-foundation.ts +56 -0
  23. package/server/routes/health.ts +4 -3
  24. package/server/routes/meetings.ts +24 -59
  25. package/server/routes/work-board.ts +79 -0
  26. package/server/routes/work-reviews.ts +24 -0
  27. package/server/scripts/control2-draft-canary.ts +18 -0
  28. package/server/scripts/control2-foundation-lab.ts +45 -0
  29. package/server/scripts/control2-sandbox-canary.ts +4 -0
  30. package/task-runtime/LICENSE +21 -0
  31. package/task-runtime/manifest.json +37 -0
  32. package/task-runtime/task_runtime.py +380 -0
  33. package/work-task-runtime/cos_atomic.py +90 -0
  34. package/work-task-runtime/manifest.json +33 -0
  35. package/work-task-runtime/runtime_io.py +27 -0
  36. package/work-task-runtime/task_bridge.py +87 -0
  37. package/work-task-runtime/task_checkout.py +380 -0
  38. package/work-task-runtime/task_commands.py +275 -0
  39. package/work-task-runtime/task_dedup.py +11 -0
  40. package/work-task-runtime/task_rows.py +420 -0
  41. package/work-task-runtime/task_work_metadata.py +107 -0
  42. package/work-task-runtime/task_write.py +645 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ ## 6.56.0
2
+
3
+ ### Connected Work across COS Control and glasses
4
+
5
+ - Work groups canonical tasks into Mentioned, Planned, Draft, Built, QA and Complete. Stable Work identities preserve existing task IDs. Revision checks and shared task locks refuse stale or uncertain writes.
6
+ - Confirmed meeting references connect tasks to the saved meeting library. Exact provider-qualified native handoff receipts can be read by the phone and glasses; delivered context is not a claim that a task has finished.
7
+ - Manual saved-meeting review is available through the authenticated Work review API, with durable deduplication and source-revision checks. Reviews propose next steps; they do not automatically create tasks, prepare work, send to sessions or publish. Set COS_WORK_REVIEWS_ENABLED=0 to disable reviews.
8
+ - Fresh installs can use a bundled, hash-verified canonical task runtime when no full COS bridge is configured. It requires Python 3.10+ and supports the existing task API as well as Work. A broken configured bridge remains an error instead of silently changing stores.
9
+ - Corrupt lock ownership, invalid task inventories and malformed native journals fail closed. Native activity returns bounded receipt metadata only, without prompts, drafts or result bodies.
10
+ - COS Glasses 6.9.556 adds the domain/stage Work view. Earlier glasses clients retain their existing task routes. COS Control 0.5.240 provides the integrated Work workspace and explicit Continue, Fork and New session actions.
11
+
12
+ ## 6.55.1-control2-foundation.0
13
+
14
+ ### Isolated COS Control 2 foundation candidate
15
+
16
+ An opt-in local lab turns manually replayed synthetic meeting evidence into durable review packets. It runs on a separate loopback port with a private scratch-home token and does not install over, restart or launch the managed production service.
17
+
18
+ - Durable revision receipts deduplicate unchanged replays, reject conflicting content or source aliases, and supersede old review records. Automatic meeting consumption, legacy task migration and automatic execution remain unavailable.
19
+ - The native Foundation Lab can request one explicitly enabled, no-tool Claude text preview per backend process. A fixed renderer escapes the response into local HTML; a sandboxed readback checks the artifact. This is draft copy, not website coding or a deployed page.
20
+ - Publication is unavailable. Source revision checks fence late draft results; the native opener verifies the local preview's path and content hash. Provider tool containment, production source adapters and a publisher remain later gates.
21
+ - The package includes the standalone lab entrypoint, sandbox spike and portable task-read foundation. This prerelease is a test candidate; the installed Control, server and glasses baseline remain unchanged.
22
+
23
+ ## 6.55.0
24
+
25
+ ### Longer lines behind the prompt box, and a card for a meeting
26
+
27
+ COS Glasses 6.9.546 docks the card behind the prompt box and gives its first two lines two rows each. The server now writes each line to the length the phone asks for, and writes a card for a meeting from the meeting's own notes. The older app is unaffected: 6.9.545 sends neither, and gets exactly what 6.54.0 gave it.
28
+
29
+ - **`chars` on `POST /api/lens-gist`:** `{"first": 100, "soWhat": 100, "third": 50}` asks for lines up to that many characters. Each must be a whole number and is held to its bounds (`first` and `soWhat` 20 to 150, `third` 20 to 80, all under the 160 characters a card line may hold); anything else (a string, an array, a fraction, a missing number) is a 400. `null` is the same as leaving it out.
30
+ - **No change for the older app.** Without `chars` the prompt is byte for byte what 6.54.0 sent (a test holds it to 6.54.0's text and passes against 6.54.0's own code), and the cache key is 6.54.0's, so cards already cached still answer. A budget is part of the key, so a long card never answers a phone that asked for a short one.
31
+ - **`kind: "meeting"`:** the phone sends a meeting's own notes (title, time, summary, decisions and action items) as `reply`, with no `ask`. The card is `Gist` (what the meeting settled or covered, returned as `outcome`), `So what` (what it means for you; empty when nothing is needed) and `Open` (one item still unresolved, returned as the new optional `open`; "nothing open", "none" and "no open items" leave it out). An `ask` sent with a meeting is dropped, the notes are data the model is told not to follow, and text in them cannot close their `<meeting>` tag. An `Open` line a model adds to a session or message card is dropped, so those cards are unchanged.
32
+ - Checks: 15 new tests (13 on the library, 2 on the route). 14 new mutation cases (the budget's bounds, `null` as absent, the budget reaching the prompt and the cache key, the 6.54.0 escape, and the meeting's prompt, ask, tag, `Gist` and `Open` lines), run with the 50 from 6.54.0: 64 of 64 killed from a green baseline. Full suite 5,229 passed, 2 skipped (343 files); typecheck clean; `npm audit --omit=dev` 0 vulnerabilities.
33
+
1
34
  ## 6.54.0
2
35
 
3
36
  ### The summary behind the G2 prompt box, from the engine you choose
package/README.md CHANGED
@@ -308,6 +308,22 @@ that card: `Outcome` (`Answer` for a message), `So what` and `You asked`, one le
308
308
  each, instead of the reply's first lines. Older phones and older servers keep the
309
309
  6.9.544 card.
310
310
 
311
+ **Longer lines, and meetings (6.55.0).** A phone with more room can say how long each line
312
+ may run, with `"chars": {"first": 100, "soWhat": 100, "third": 50}` in the request (COS
313
+ Glasses 6.9.546 asks for these, giving the first two lines two rows each). Each number must
314
+ be a whole number and is held to its bounds: 20 to 150 for the first line and `So what`, 20
315
+ to 80 for the third. Anything else is a 400. Without `chars`, or with `null`, the lines keep
316
+ their 6.54.0 lengths and the prompt is word for word what 6.54.0 sent, so 6.9.545 sees no
317
+ change. The budget is part of the cache key, so a card written long is never handed to a
318
+ phone that asked for a short one.
319
+
320
+ `"kind": "meeting"` writes a meeting's card from the meeting's own notes (title, time,
321
+ summary, decisions and action items), sent as `reply` with no `ask`: `Gist` (what the meeting
322
+ settled or covered, returned as `outcome`), `So what` (your next steps, left empty when
323
+ nothing is needed) and `Open` (one question still unresolved, returned as `open` and left out
324
+ when nothing is open). An `ask` sent with a meeting is ignored, and the notes are data the
325
+ model is told not to follow, the same as a reply.
326
+
311
327
  **Pick the engine** the way the COS indexer does: `COS_LENS_GIST_ENGINE` wins, then the
312
328
  choice saved with `PUT /api/lens-gist/config`, then the default. One engine never falls back
313
329
  to another; `off` turns the summary off.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+ // Explicit lab entrypoint. Never imports or starts the managed production daemon.
4
+ const { spawn } = require('node:child_process')
5
+ const { resolve } = require('node:path')
6
+ const root = resolve(__dirname, '..')
7
+ const child = spawn(process.execPath, ['--import', require.resolve('tsx/esm'), resolve(root, 'server/scripts/control2-foundation-lab.ts')], {
8
+ cwd: root, env: process.env, stdio: 'inherit',
9
+ })
10
+ child.on('error', error => { console.error(error.message); process.exitCode = 1 })
11
+ child.on('exit', (code, signal) => { process.exitCode = code ?? (signal ? 1 : 0) })
12
+ for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => child.kill(signal))
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+ // Candidate installer/probe. Does not configure the live COS bridge.
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+ const { createHash } = require('node:crypto')
7
+ const { spawnSync } = require('node:child_process')
8
+ const flags = process.argv.slice(2)
9
+ const opts = {}
10
+ for (let i = 0; i < flags.length; i++) {
11
+ if (flags[i] === '--install') { opts.install = true; continue }
12
+ if (!['--runtime-dir', '--data-root', '--domain', '--operator', '--python'].includes(flags[i]) || !flags[i + 1] || flags[i + 1].startsWith('--')) {
13
+ console.error('Usage: node bin/task-runtime-check.cjs --runtime-dir /absolute/runtime [--install] [--data-root /absolute/data --domain work] [--operator Name] [--python python3]')
14
+ process.exit(2)
15
+ }
16
+ opts[flags[i].slice(2)] = flags[++i]
17
+ }
18
+ function verify(dir) {
19
+ const manifestPath = path.join(dir, 'manifest.json')
20
+ if (fs.lstatSync(manifestPath).isSymbolicLink()) throw new Error('Symlinked manifest refused')
21
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
22
+ if (manifest.schema !== 1 || manifest.protocol !== 'cos-control-task-read/1' || manifest.readOnly !== true ||
23
+ Object.keys(manifest.files).sort().join(',') !== 'LICENSE,task_runtime.py') throw new Error('Unsupported runtime manifest')
24
+ for (const [name, hash] of Object.entries(manifest.files)) {
25
+ const file = path.join(dir, name)
26
+ if (!fs.lstatSync(file).isFile() || fs.lstatSync(file).isSymbolicLink()) throw new Error('Nonregular runtime member')
27
+ if (createHash('sha256').update(fs.readFileSync(file)).digest('hex') !== hash) throw new Error('Runtime checksum mismatch')
28
+ }
29
+ return manifest
30
+ }
31
+ try {
32
+ if (!opts['runtime-dir'] || !path.isAbsolute(opts['runtime-dir'])) throw new Error('Absolute runtime directory required')
33
+ const runtime = path.resolve(opts['runtime-dir'])
34
+ const data = opts['data-root'] && path.resolve(opts['data-root'])
35
+ if (data && (!path.isAbsolute(opts['data-root']) || runtime === data || data.startsWith(runtime + path.sep) || runtime.startsWith(data + path.sep))) throw new Error('Runtime and data roots must be separate')
36
+ if (fs.existsSync(runtime) && fs.lstatSync(runtime).isSymbolicLink()) throw new Error('Symlinked runtime directory refused')
37
+ if (opts.install) {
38
+ const source = path.resolve(__dirname, '../task-runtime')
39
+ const manifest = verify(source)
40
+ if (fs.existsSync(runtime) && fs.readdirSync(runtime).length) throw new Error('Install destination must be empty')
41
+ fs.mkdirSync(runtime, { recursive: true, mode: 0o700 })
42
+ for (const name of [...Object.keys(manifest.files), 'manifest.json']) fs.copyFileSync(path.join(source, name), path.join(runtime, name), fs.constants.COPYFILE_EXCL)
43
+ }
44
+ verify(runtime)
45
+ const probe = (args) => {
46
+ const result = spawnSync(opts.python || 'python3', ['-I', '-S', path.join(runtime, 'task_runtime.py'), ...args], {
47
+ cwd: runtime, env: { PATH: process.env.PATH || '/usr/bin:/bin', LANG: 'en_US.UTF-8' },
48
+ encoding: 'utf8', timeout: 15000, maxBuffer: 16 * 1024 * 1024,
49
+ })
50
+ if (result.error) throw result.error
51
+ if (result.status !== 0) throw new Error(`Runtime probe failed: ${result.stdout.trim() || result.stderr.trim().slice(0, 300)}`)
52
+ return JSON.parse(result.stdout)
53
+ }
54
+ const capabilities = probe(['capabilities'])
55
+ if (capabilities.protocol !== 'cos-control-task-read/1' || capabilities.writes !== false) throw new Error('Capability mismatch')
56
+ const result = { ok: true, installed: !!opts.install, capabilities }
57
+ if (data) {
58
+ result.domains = probe(['domains', '--root', data]).domains
59
+ if (opts.domain) result.tasks = probe(['task-rows', '--root', data, '--domain', opts.domain, ...(opts.operator ? ['--operator', opts.operator] : [])])
60
+ } else if (opts.domain) throw new Error('--domain requires --data-root')
61
+ console.log(JSON.stringify(result))
62
+ } catch (error) {
63
+ console.error(JSON.stringify({ ok: false, error: error.message }))
64
+ process.exit(1)
65
+ }
@@ -0,0 +1,79 @@
1
+ # Control 2 foundation candidate
2
+
3
+ This opt-in development slice proves durable source receipts, a separate native
4
+ review surface, isolated execution primitives and portable task-read feasibility.
5
+ It does not implement automatic agent building or publication. Neither the
6
+ managed server nor its task scheduler is started by the lab launcher.
7
+
8
+ ## Start the disposable backend
9
+
10
+ From this package, with Node dependencies installed:
11
+
12
+ ```sh
13
+ export COS_CONTROL2_FOUNDATION=1
14
+ export COS_CONTROL2_DRAFT_ENABLED=1
15
+ export COS_CONTROL_TEST_HOME="$(mktemp -d /tmp/cos-control2-lab.XXXXXX)"
16
+ export COS_CONTROL_TEST_API_PORT=3147
17
+ npm run control2:lab
18
+ ```
19
+
20
+ The same scratch home, API port and foundation flag launch the native Foundation Lab candidate.
21
+ The launcher writes a private token under the scratch home. It does not print
22
+ the token or reuse production configuration. Only localhost is bound; browser
23
+ Origin requests and unauthenticated calls are denied. Ports 3141/3143 are refused.
24
+
25
+ Replay the synthetic meeting twice: one item remains. Advance its revision: the
26
+ old item is superseded. Restart the backend using the same test home: receipts
27
+ and review items persist. Source readiness false creates a blocked item. A
28
+ revision reused with different content is rejected. Publication always refuses.
29
+
30
+ ## Manual text preview
31
+
32
+ Select a current review item and choose **Prepare text preview**. This explicitly
33
+ starts one authenticated Claude CLI turn with tools disabled. The model returns
34
+ plain title/body fields; deterministic escaping and a restrictive CSP produce
35
+ HTML, then an OS-sandboxed command verifies the bytes. **Open checked preview**
36
+ checks the retained file hash before opening it. No website code is changed.
37
+
38
+ The draft flag is optional and off by default. There is a maximum of one started
39
+ model turn per backend process, a $1 CLI budget ceiling and a 120-second maximum.
40
+ Restart the isolated backend to make another attempt. Existing checked previews
41
+ are reused without another turn. Instructions, including source and criteria,
42
+ are capped at 4,000 characters; oversized inputs show a draft validation error.
43
+ A missing or changed persisted preview fails closed with a recovery error.
44
+
45
+ ## Storage decision
46
+
47
+ Use one versioned, bounded journal and durable fsync/rename writes for the
48
+ foundation spike. All transitions synchronously acquire an exclusive journal
49
+ lock, then load, validate and replace the complete record. There are at most 200
50
+ receipts; capacity fails closed rather than forgetting deduplication history.
51
+ An unclean writer lock or corrupt/unsupported journal returns a recovery error.
52
+ Do not delete that lock until the owner process is confirmed stopped and the
53
+ journal inspected. This is deliberately not automatic crash recovery for the
54
+ full MVP. Full task migration/cross-store transactions and lease fencing remain
55
+ the next slice's gates; decide on SQLite before those if a file transaction
56
+ cannot meet the validated cutover contract.
57
+
58
+ Meeting revisions are opaque and manual lab replay is explicitly ordered by the
59
+ tester. This is not an out-of-order production event consumer. Late source alias
60
+ conflicts stop for reconciliation rather than silently unifying identities.
61
+ No legacy tasks, existing sessions, memories or meeting source files are written.
62
+
63
+ ## Rollback
64
+
65
+ Stop the lab backend and quit the separate lab app. The installed Control
66
+ 0.5.239, managed server 6.55.0 and glasses 6.9.555 baseline need no rollback because
67
+ the lab never replaces them. Retain scratch data for diagnosis or remove that
68
+ specific disposable directory after inspection. All repositories have the
69
+ `cos-control2-baseline-2026-09-27` annotated tag. Runtime artifact checksums and
70
+ operator restore instructions live in the private COS baseline receipt.
71
+
72
+ ## Remaining gates
73
+
74
+ The OS sandbox proof is for fixed uncredentialed system commands, not proof that
75
+ Claude/Codex can safely execute tools with their normal configuration. The
76
+ portable task package reports its actual capabilities and does not perform a
77
+ legacy stable-ID cutover. Production source adapters, exact pilot meeting and
78
+ website binding, actual-theme staging, managed provider execution, revision-bound
79
+ approval and a publisher are later gated slices. Do not advertise them as shipped.
@@ -0,0 +1,47 @@
1
+ # Control 2 foundation: isolated preparation capability
2
+
3
+ Status: foundation spike, September 27, 2026. This is an importable macOS command-probe API with an actual OS policy. It is not a Claude/Codex adapter, website builder, or publication API. No deployed application behavior changes merely by importing this module.
4
+
5
+ ## Decision
6
+
7
+ Use a fresh copied snapshot with an explicit approved file allowlist, not a linked Git worktree, as the containment unit. Never copy `.git`, hidden configuration, credential filenames, symbolic links, hardlinks, devices or directories supplied as files. Copying does not change the original checkout. The snapshot lives in a private temporary parent and gets a private HOME and TMPDIR.
8
+
9
+ Only the server that created the in-memory snapshot handle can run it. Arbitrary client-provided root paths are rejected. One process run owns a snapshot at a time, and disposal while running is rejected. No endpoint should accept an executable, source root or file allowlist directly from an untrusted client.
10
+
11
+ For the future Git target adapter, the approved project configuration must identify the repository and exact base commit. Materialize selected tracked files from that commit into an owned staging source, then call this API. Record commit, file hashes and project-policy revision in the parent journal. Dirty source files must remain untouched. A live developer checkout is not an immutable approved commit: the caller must supply an exclusively owned, stable source while copying. This spike does not implement Git revision selection or defeat a hostile process racing source-directory mutations.
12
+
13
+ ## OS boundary
14
+
15
+ `/usr/bin/sandbox-exec` receives a generated deny-default profile. The policy permits content reads only inside the snapshot and fixed OS library/executable paths. Filesystem writes are limited to the snapshot and `/dev/null`. Network access and Mach service access have no allow rule. The process receives five explicit environment values, never the server environment, provider authentication or connector settings. Standard input is closed and only stdout/stderr pipes are inherited.
16
+
17
+ Modern macOS dyld needs permission to read the literal root directory. That does not allow its descendants. Global file metadata and sysctl reads are allowed and should not be described as complete host invisibility. `/System/Library` is allowed rather than all `/System`, which can expose the Data volume through another spelling.
18
+
19
+ Executable admission is deliberately limited to `/bin/sh`, `/bin/bash`, `/bin/cat`, `/bin/echo`, `/bin/sleep`, `/bin/ln` and `/usr/bin/env`. Child exec uses the same fixed list. A provider binary, interpreter, Git, package manager or build tool is unsupported and fails closed. Shell descendants inherit the OS sandbox. The process runs in a new group; timeout, cancellation, excess output and normal completion terminate remaining group members. Output is limited to 64 KiB and deadlines to 30 seconds. This is not a generalized resource-isolated VM or fork-bomb-resistant agent runtime.
20
+
21
+ The capability must remain disabled for actual provider execution until a separate adapter proves provider authentication without giving the child host secrets, tool/file access, network destinations, descendants and revision-bound outputs. Passing these probe commands cannot establish that broader capability.
22
+
23
+ ## API
24
+
25
+ - `createControl2Snapshot({sourceRoot, relativePaths})` copies an explicit safe set and returns `{id, root, files, dispose}`. Omit input for an empty disposable probe. Total source data is limited to 16 MiB and 1,000 files. Filenames alone cannot establish whether content contains secrets; the caller approves content scope.
26
+ - `runControl2Sandbox({snapshot, executable, args, timeoutMs, signal})` returns exit status, bounded output, duration and timeout/cancellation/output-limit flags. Unsupported platform/root/executable or failed sandbox launch never falls back to unsandboxed execution.
27
+ - `probeControl2Sandbox()` creates only disposable files and a loopback test listener. It returns `supported`, `proven`, `code`, named checks and limitations. Treat `proven:false` as unavailable. Results are specific to this host, OS policy and current executables, not a permanent provider entitlement.
28
+
29
+ ## Verification
30
+
31
+ Run `node --import tsx/esm server/scripts/control2-sandbox-canary.ts` from the server repository. It exits nonzero unless every check passes. The imported probe does not print file contents or credentials.
32
+
33
+ The canary demonstrates a real positive workspace read/write, a host-readable protected sentinel, and a loopback server reached outside the sandbox by the same Bash network builtin. It then proves denied host reads/writes, denied symlink escape and a denied network connection with no new server hit. Timeout and cancellation checks record a child PID and verify it no longer exists. Additional checks cover minimal environment and bounded output. A broken network client is not counted as isolation.
34
+
35
+ Run `npx vitest run server/lib/control2-sandbox.test.ts --maxWorkers=1`. The suite additionally rejects traversal, hidden/credential paths, symbolic/hard links, arbitrary roots/executables, invalid deadlines, concurrent ownership and active disposal. OS-level cases run only on macOS; unsupported systems do not receive a passing capability result.
36
+
37
+ Observed in this session: 14 tests passed and all 14 live probe checks passed. Production provider tools, authenticated connectors, live CMS writes, Git deploy and publication were not exercised. The parent journal, policies and native surface remain independently owned work.
38
+
39
+ ## Bounded no-tool draft adapter
40
+
41
+ `control2-draft.ts` adds a separate capability: `prepareControl2Draft({instruction, model?, timeoutMs?, signal?})`. It is off unless the trusted harness sets `COS_CONTROL2_DRAFT_ENABLED=1`, permits one live turn per server process in this foundation spike, and refuses concurrent work. The configured Claude model is used; otherwise the repository default applies. It does not select a cheaper readiness model. A durable project budget must replace this development cap before routine enablement.
42
+
43
+ The authenticated CLI broker runs outside the OS sandbox because it needs its account and provider service. It receives safe mode, explicit empty strict MCP configuration, empty built-in and allowed tool lists, `dontAsk`, no session persistence, one-turn and $1 CLI budget flags, a bounded prompt and 120-second maximum deadline. The $1 flag is the CLI's reported API budget behavior; it is not a guarantee about subscription billing. The CLI and its authentication are part of the trusted host boundary. No model-selected executable or path is accepted. Timeout/cancel terminates the provider process tree; uncertain termination retains ownership and the snapshot.
44
+
45
+ The response must be JSON with exactly two strings, `title` and `body`; extra keys, malformed JSON, control characters and excess length are refused. A deterministic renderer HTML-escapes both strings into a fixed CSP-protected template, writes `preview.html` with exclusive creation into an internally created snapshot, and verifies exact content using the OS-constrained fixed command lane. The returned artifact includes its hash, model, scope and disposal function. This proves useful text preparation, not autonomous code changes or publishing. It never resumes a user conversation.
46
+
47
+ Run `COS_CONTROL2_DRAFT_ENABLED=1 node --import tsx/esm server/scripts/control2-draft-canary.ts` intentionally; it makes one real provider call using only synthetic data and cleans its artifact afterward. The observed call returned the requested `Preview ready` heading with `sonnet[1m]`, passed exact artifact verification, and left a disposable production sentinel unchanged. Five draft checks passed. The hostile script/path/command test feeds the deterministic renderer directly; it does not claim a second model jailbreak test. Ten draft unit tests plus fourteen sandbox tests passed, and repository TypeScript checking passed. Tool-enabled provider execution, live meeting ingestion and publication remain separate capabilities.
@@ -0,0 +1,68 @@
1
+ # ADR: portable task runtime foundation
2
+
3
+ Status: accepted for a read-only foundation candidate; production writes and migration remain disabled. September 27, 2026.
4
+
5
+ ## Decision
6
+
7
+ Ship a small standalone Python reader with protocol `cos-control-task-read/1`, installed and probed by `bin/task-runtime-check.cjs`. Python 3.10+ and Node are required; no pip, venv, private COS checkout, credentials, vector database or model invocation is needed. The CLI does not change the live bridge configuration or task capability gate.
8
+
9
+ The candidate supports exactly:
10
+
11
+ | Command | Contract |
12
+ |---|---|
13
+ | capabilities | Explicit protocol/version, commands, read-only flag, unstable identity type, byte limit and disabled write/dispatch/migration flags |
14
+ | domains | Discover valid domain directories containing readable regular tasks.md files under the explicit root; no personal-domain fallback |
15
+ | task-rows | Canonical row fields, source SHA-256 revision and legacy description-hash IDs |
16
+
17
+ `writes`, `dispatch` and `migration` are false. An installed file named a bridge is not evidence of CRUD support. Do not connect this candidate to task-store's existing `pythonBridgeAvailable()` gate. Consumers must negotiate protocol and command capabilities.
18
+
19
+ ### Install and inspect
20
+
21
+ Choose distinct absolute paths for runtime code and disposable data. Runtime installation requires an empty destination and does not overwrite an existing package.
22
+
23
+ ```sh
24
+ node bin/task-runtime-check.cjs --install --runtime-dir /tmp/cos-task-candidate/runtime --data-root /tmp/cos-task-candidate/data --domain demo
25
+ node bin/task-runtime-check.cjs --runtime-dir /tmp/cos-task-candidate/runtime --data-root /tmp/cos-task-candidate/data --domain demo
26
+ python3 -I -S /tmp/cos-task-candidate/runtime/task_runtime.py capabilities
27
+ ```
28
+
29
+ Create `data/demo/tasks.md` before invoking the read probe. `--operator Alex` optionally selects first-name ownership behavior; with no operator, explicitly named owners are conservatively delegated. The protocol reads domain names matching ASCII letters/digits/underscore/hyphen, bounded to 128 characters. Nonmatching names are unsupported by this candidate, not proof of missing source data.
30
+
31
+ The installer verifies fixed manifest member names and their checksums before execution. This checks package integrity against its manifest, not authenticity against a maliciously replaced manifest. Trust comes from the installed application/package distribution. It launches Python with `-I -S`, a cleared environment except PATH/LANG, bounded time/output, and the installed directory as cwd. Source access requires an explicit absolute root, descriptor-relative no-follow domain/task opens, regular-file checks and an 8 MiB cap. It detects observed in-place size/mtime changes and asks the caller to retry. Directory ancestors of the explicitly provided roots remain operator-trusted. This is a read-only source adapter, not a provider execution sandbox.
32
+
33
+ ## Provenance and complete import inventory
34
+
35
+ `task-runtime/manifest.json` records the source commit and hash, adaptations, version and every shipped file hash. Source: MU-Chief-Staff snapshot `1dac83bd62937717e460f9a8334c054f79011128`, `operations/scripts/task_rows.py`; normalization is the exact lowercase/punctuation/whitespace behavior of `TaskDeduplicator.normalize` from `task_dedup.py`.
36
+
37
+ The parser keeps checkbox/archive states, source/finish-line splitting, owner/delegation detection, section and schedule markers, review state and duplicate-description ordinals. Personal domain fallbacks and a hardcoded operator were removed. The command envelope and file-access layer are new. The original `task_dedup` import is replaced by the small normalization function; no LLM deduplication code is included.
38
+
39
+ Complete runtime imports are Python standard library: argparse, hashlib, json, os, re, stat, string, sys, dataclasses, pathlib and typing. The Node installer imports only node:fs, node:path, node:crypto and node:child_process. No private configuration, meetings, cache files, data samples or credentials ship. The package carries the repository's MIT license and its existing attribution.
40
+
41
+ The legacy source parser and this package can drift. Changes to marker grammar, normalization, owner parsing or task metadata must deliberately update this package's version/provenance and fixture assertions. Private-source parity is optional supplementary evidence; installed-package tests must always run independently.
42
+
43
+ ## Stable identity and write migration design
44
+
45
+ The read candidate deliberately exposes legacy identity instability. A text rename changes its ID. These IDs must never authorize durable work or publication. CRUD and migration must be a separately negotiated future protocol, not silently added under read/1.
46
+
47
+ Planned identity representation: a UUID carried on each task line in an explicit `[task-id UUID]` metadata marker, removed from displayed description and normalized dedup text by the new parser. Stable task identity is independent of domain, task text, section, order and completion state. Validate duplicate UUIDs as corruption/ambiguity; never silently choose the first. New captures allocate identity once inside the guarded write. Every writer must preserve markers byte-for-byte unless performing the controlled migration. Older code is not allowed to mutate after cutover; version negotiation and an exclusive maintenance cutover are prerequisites, because existing marker parsers would otherwise treat the new marker as description.
48
+
49
+ 1. Fence legacy and managed admissions. Inventory and stop incompatible direct task writers, including pipeline jobs/older clients. Drain active runs before acquiring short-lived domain write locks; otherwise retain them with an explicit old-ID alias mapping. Never hold file locks while waiting on a model.
50
+ 2. Acquire domain write locks and obtain a consistent task/ledger snapshot. Allocate UUIDs using exact source revision plus row identity, including duplicate ordinal, only within this snapshot. Recheck hashes immediately before writes. Ambiguous historical references remain unresolved rather than guessed.
51
+ 3. Persist a migration intent containing backup hashes, the one-to-one alias map, source revisions and intended writes. Update rows and mutable task references, pending schedules, captures-seen and lock references. Historical audit/completion records can retain old references resolved through the immutable alias map.
52
+ 4. Never regenerate existing clientJobId, jobId, run identity, sessionId, generation or publication receipt. Running ledger references resolve through the alias map until reconciled. A migration is not a retry of the underlying job.
53
+ 5. Commit the migration receipt, reconcile active jobs and row ownership, then resume compatible admission. Recover interrupted transactions from intent plus observed hashes; refuse unknown mixed state. Persistent IDs and migration records must be written durably with compare-and-swap revisions.
54
+ 6. A durable owner/fence keyed by stable task ID arbitrates legacy and managed execution. Scheduled selection, manual run, run minting and legacy orphan reconciliation must all honor it. File-write locks and run leases are separate mechanisms. Lease expiry does not authorize accepting late output or blindly launching another process.
55
+ 7. Cross-domain movement is a two-domain guarded transaction preserving UUID. The existing writer's `move()` only changes section inside one domain. Reordering, duplicate descriptions, archive and completion preserve UUID and provenance. Active work cannot be silently archived or reidentified.
56
+ 8. Rollback first fences admission. Run a versioned compatibility conversion that preserves post-migration edits, aliases and receipts. Do not restore a baseline backup over newer work. Reject downgrade when an older reader/writer cannot preserve stable identity or ownership. Resolve ordinal mutation requests against a matching board revision or reject them.
57
+
58
+ Required coordinated consumers: task_rows, task_write, task_updater, cos_api_bridge, task_checkout, task_promoter, task_archiver, all direct writer inventory; server task-store/dispatcher/routes and legacy run/capture ledgers; native Models/ControllerModel/helper. The old task_updater constructs ordinal IDs even when the parser supplies hashes, so parser changes alone are insufficient.
59
+
60
+ Required future migration canaries: restart at each cutover write, existing active legacy job, scheduled/manual/managed admission race, text edits, reordered duplicate descriptions, cross-domain transfer, archive/completion, stale ordinal mutation and rollback after new edits. None is claimed implemented by this reader package.
61
+
62
+ ## Verification recorded
63
+
64
+ `npx vitest run server/lib/control2-task-runtime.test.ts --maxWorkers=1`: 4/4 passed on September 27, 2026. Checks cover independent installation, row/owner/source/finish-line/schedule semantics, distinct duplicate IDs, unchanged source, refusal to overwrite installed runtime, explicit unstable rename behavior, unsupported mutation, missing root, traversal/symlink rejection and checksum failure before Python execution.
65
+
66
+ A separate clean-directory canary installed the three-file package into a temporary directory, launched from outside the repository with only PATH supplied, read one task in a new `demo` domain, confirmed source hash unchanged and removed the temporary directory. Result: PASS. No COS environment variables, user data, private imports or model calls were used.
67
+
68
+ Unproven: production CRUD, stable-ID migration, live bridge integration, a complete clean-user macOS application installation, provider containment and meeting-to-publication execution. These remain explicit later gates. This candidate is not a claim that the complete standalone product is ready.
@@ -0,0 +1,25 @@
1
+ # Saved-meeting reviews in Work
2
+
3
+ Work reviews add an optional, durable read-only review step over the existing saved-meeting library. They do not migrate or rewrite tasks, and do not change the glasses Tasks contract.
4
+
5
+ ## Enablement
6
+
7
+ Manual review is enabled by default. Set `COS_WORK_REVIEWS_ENABLED=0` to disable it. Opening or starting the server does not submit a review; admission requires an explicit manual request. The existing authenticated API exposes `GET/POST /api/work-reviews` and `GET /api/work-reviews/:id`. A disabled or degraded review runtime returns 404; base APIs continue to work. The native client retains normal tasks and meetings and explains review unavailability.
8
+
9
+ A request contains an explicit configured model slot and canonical meeting descriptor (`domain`, `month`, `filename`, optional `recordId`). It resolves full saved source through the same resolver as Meetings. Source truncation/absence is refused; long review inputs disclose excerpt coverage. Proposed links use same-domain source references or exact normalized action text. They are not semantic deduplication or authority to mutate a task.
10
+
11
+ Each review persists intent before job admission. Canonical record, source revision and policy version deduplicate repeated requests. Model changes do not create a second review for the same revision. Unconfirmed admission retains its original identity and does not automatically resend. Startup/timer reconciliation runs independently of native window lifetime. Source changes supersede results. A temporary source outage preserves terminal evidence privately and restores it only after the same identity/revision can be revalidated, including after query-job expiry.
12
+
13
+ The API uses the existing durable query coordinator. Claude review restricts tools to Read/Grep/Glob; Ollama is text-only. Codex and Cursor are unavailable for this extraction endpoint until an appropriately restricted adapter is qualified. These restrictions do not change the separate explicit session-handoff capabilities in COS Control. Only the local Ollama review route received a live canary in this change; fixture/source checks do not establish live compatibility for every model tier.
14
+
15
+ Review output currently appears in the existing COS conversation/display as well as Work. Automatic after-sync execution, automatic task mutation and publication are false capabilities. Session preparation remains an explicit user action with the destination's existing permissions and independent approval requirements.
16
+
17
+ ## Recovery
18
+
19
+ Review journal lives under the resolved COS data directory's `work-reviews` folder. One runtime owns it. A corrupt/unsafe/locked optional journal disables reviews and emits a generic diagnostic; it does not prevent the base server from starting. Preserve the journal for diagnosis. Do not delete unconfirmed intents to obtain a retry.
20
+
21
+ ## Verification
22
+
23
+ `npx vitest run server/lib/work-review-runtime.test.ts server/routes/work-reviews.test.ts server/routes/meetings.test.ts --maxWorkers=1` covers the new runtime and existing canonical meeting routes. `node server/scripts/work-review-mutation-gate.mjs` runs six mutations in a private copy without editing a live checkout. `npm run typecheck` and the full test suite remain release gates.
24
+
25
+ `server/scripts/work-review-candidate.ts` is a local development qualification gateway, excluded from the npm package. It preserves installed services and admits only Ollama through the existing public query API. Its private loopback token never replaces the installed pairing token. It is not a production server or a public installer.
@@ -0,0 +1,82 @@
1
+ # Portable canonical Work tasks
2
+
3
+ The production task adapter first uses an available configured COS Python bridge.
4
+ If `COS_SCRIPTS_DIR` is configured but broken, it fails closed. It does not silently
5
+ switch that install to another storage system. Otherwise the npm package includes
6
+ `work-task-runtime`, a standard-library-only Python 3.10+ task bridge. The server
7
+ probes the interpreter, uses `-I -S -B`, verifies packaged file hashes before every
8
+ invocation, bounds input/output/time, and passes a minimal child environment.
9
+ Unsupported platforms/interpreters remain unavailable. Python is a prerequisite;
10
+ this package does not silently install an interpreter. A stock Mac whose only
11
+ interpreter is Apple's Python 3.9 does not meet this prerequisite. With Homebrew
12
+ already installed, run `brew install python@3.12`, then restart the server. Discovery
13
+ checks unversioned Homebrew Python and exact Apple Silicon/Intel Homebrew opt/bin
14
+ paths for Python 3.12 and 3.11 before `/usr/bin/python3`; it does not assume the
15
+ versioned formula creates an unversioned `python3` link. An explicit
16
+ `COS_TASK_PYTHON` is exclusive and must point to a supported executable. If
17
+ `COS_SCRIPTS_DIR` is set but broken, repair that bridge instead: Work deliberately
18
+ does not use the portable fallback to hide a configured pipeline failure.
19
+
20
+ The source is the same canonical `tasks.md`, not a new database. Resolution is an
21
+ explicit `COS_OPERATIONS_DIR` (including a new empty root), the existing operations
22
+ resolver, then `COS_DATA_DIR/operations`. Configured/discovered/default domains use
23
+ the shared server domain resolver. A fresh default domain can receive its first
24
+ capture. `COS_PORTABLE_TASKS=0` disables only the portable fallback; an existing
25
+ full COS bridge remains authoritative. `COS_TASK_PYTHON` can select an absolute
26
+ interpreter path. Explicit roots and source files must not be symlinks; configure
27
+ the real directory path. Root/domain opens are pinned descriptors; on macOS the
28
+ single-command child pins its domain as cwd instead of relying on unsupported
29
+ `/dev/fd/<directory>/file` traversal. Source reads use no-follow bounded opens;
30
+ atomic replacement writes use exclusive no-follow temporary files. Same-user
31
+ modification of the installed package/manifest is outside this integrity boundary.
32
+
33
+ The default lock namespace remains `~/Library/Application Support/COS/.task_locks.json`
34
+ and canonical `file:<domain>` entries. `COS_TASK_LOCK_STORE` is an explicit override
35
+ for isolated fixtures. Existing COS bridge calls keep their original environment
36
+ and lock behavior. Portable calls pin the lock directory and reject symlink,
37
+ nonregular, oversized or corrupt lock stores. Do not delete locks to retry a write.
38
+
39
+ `server/scripts/build-work-task-runtime.py --source /absolute/COS/operations/scripts`
40
+ rebuilds the snapshot. `manifest.json` records source commit, source hashes, exact
41
+ packaged hashes and adaptations. Canonical parser/writer/metadata/checkout/atomic
42
+ modules are included; only task handlers and the exact normalization method are
43
+ extracted from larger source modules. No customer data, credentials, general COS
44
+ bridge, vector dependencies or provider invocation ships. The repository MIT
45
+ license applies. Owner matching uses the configured profile's first name; without
46
+ a configured owner, explicit named owners remain delegated. Task IDs, source,
47
+ legacy markers, stable Work metadata and CAS revisions retain canonical semantics.
48
+
49
+ Supported commands cover task-store capture, rows, edit, schedule, run markers,
50
+ legacy stage, finish line, move, check/uncheck, Work stage and confirmed meeting
51
+ link writes. Readiness does not authorize autonomous work. Server-side meeting
52
+ resolution still verifies exact canonical meeting identity before linking.
53
+
54
+ ## Native receipt activity
55
+
56
+ `GET /api/work-board/activity?domain=<domain>&workIdentity=<12hex>` uses the existing
57
+ API authentication and returns exact native receipt associations. Its `workId` is
58
+ `task:<domain>:<workIdentity>` and `sessionId`, when present, is provider-qualified
59
+ (e.g. `codex:<native-id>`). The journal path is fixed to macOS
60
+ `~/Library/Application Support/COS Control/work-handoffs/handoffs.json`. Other
61
+ platforms, isolated runtimes, missing/unsafe/malformed journals report
62
+ `available:false`, not a successful empty history. Custom data roots are treated
63
+ as isolated for native-journal reading. There is no request-supplied file path.
64
+
65
+ Only receipt identity/status/provider/session fields are projected. Prompt, detail,
66
+ result, draft and source text are never returned. Error text is generic. Session
67
+ ownership is never inferred from titles. `sending`/`preparing` project as unknown;
68
+ queued/delivered receipts are not task completion. The journal has no per-receipt
69
+ update timestamp, so no fabricated `updatedAt` is emitted. This endpoint does not
70
+ poll providers, resend work or mutate the native journal.
71
+
72
+ ## Qualification
73
+
74
+ Focused tests cover independent copied-runtime execution, fresh production-env
75
+ subprocess without test opt-in, full canonical CRUD, exact identity normalization,
76
+ metadata preservation after rename, stale CAS refusal, canonical locks, generic
77
+ owner behavior, broken-bridge refusal, root/domain/file/lock symlink refusal, input
78
+ and source limits, native receipt privacy and exact identity/auth boundaries.
79
+
80
+ The older `task-runtime` read-only foundation protocol remains a separate artifact;
81
+ it is not upgraded or advertised as writable. Public release still requires the
82
+ full test/typecheck/package gates and installed npm/Mac acceptance.
package/package.json CHANGED
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.54.0",
3
+ "version": "6.56.0",
4
4
  "description": "COS Glasses \u2014 self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, Cursor Agent CLI, or local Ollama",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "glasses-server": "bin/cli.cjs",
8
8
  "glasses-server-managed": "bin/managed-server.cjs",
9
- "glasses-memory-setup": "bin/memory-setup.cjs"
9
+ "glasses-memory-setup": "bin/memory-setup.cjs",
10
+ "cos-control2-foundation": "bin/control2-foundation.cjs"
10
11
  },
11
12
  "scripts": {
12
13
  "start": "node bin/cli.cjs",
13
14
  "start:server": "node --import tsx/esm server/index.ts",
14
15
  "test": "vitest run --maxWorkers=1",
15
16
  "typecheck": "tsc --noEmit",
16
- "mutation:gate": "node server/scripts/mutation-gate.mjs"
17
+ "mutation:gate": "node server/scripts/mutation-gate.mjs",
18
+ "control2:lab": "node bin/control2-foundation.cjs"
17
19
  },
18
20
  "keywords": [
19
21
  "even-g2",
@@ -39,6 +41,8 @@
39
41
  "shared",
40
42
  "!server/**/*.test.ts",
41
43
  "!server/scripts/engine-canary.ts",
44
+ "!server/scripts/work-review-candidate.ts",
45
+ "!server/scripts/work-review-mutation-gate.mjs",
42
46
  "!server/scripts/mutation-gate.mjs",
43
47
  "!server/lib/meeting-engine/__fixtures__/**",
44
48
  "!server/lib/__fixtures__/**",
@@ -50,7 +54,17 @@
50
54
  "CHANGELOG.md",
51
55
  "SECURITY.md",
52
56
  "bin/memory-setup.cjs",
53
- "memory-runtime"
57
+ "memory-runtime",
58
+ "bin/control2-foundation.cjs",
59
+ "bin/task-runtime-check.cjs",
60
+ "task-runtime",
61
+ "docs/control2-foundation.md",
62
+ "docs/control2-sandbox.md",
63
+ "docs/control2-task-runtime.md",
64
+ "docs/work-reviews.md",
65
+ "work-task-runtime",
66
+ "docs/work-task-runtime.md",
67
+ "!server/scripts/build-work-task-runtime.py"
54
68
  ],
55
69
  "author": "COS Contributors",
56
70
  "license": "MIT",
package/server/index.ts CHANGED
@@ -122,6 +122,9 @@ import { initializeServerInstanceId } from './lib/server-instance-id.js'
122
122
  import { appendPrivateEnvBlock, UnsafeUserConfigPathError } from './lib/secure-user-config.js'
123
123
  import { getTranscriptionProfileStatus } from './lib/profile.js'
124
124
  import { createQueryJobsRouter } from './routes/query-jobs.js'
125
+ import { createWorkReviewsRouter } from './routes/work-reviews.js'
126
+ import { createWorkBoardRouter } from './routes/work-board.js'
127
+ import { createDefaultWorkReviewRuntime } from './lib/work-review-backend.js'
125
128
  import { createMorningBriefRouter } from './routes/morning-brief.js'
126
129
  import { getMorningBriefScheduler, startMorningBriefScheduler, stopMorningBriefScheduler } from './lib/morning-brief-runtime.js'
127
130
  import {
@@ -616,6 +619,9 @@ app.use('/api', createClientInstanceRouter())
616
619
  app.use('/api', createQueryJobsRouter(queryJobCoordinator, {
617
620
  prepareAdmission: preparePublicDurableQueryAdmission,
618
621
  }))
622
+ const workReviewRuntime = createDefaultWorkReviewRuntime()
623
+ app.use('/api', createWorkReviewsRouter(workReviewRuntime))
624
+ app.use('/api', createWorkBoardRouter())
619
625
  app.use('/api', queryRouter)
620
626
  // The scheduled start-of-day brief: settings, status, run-now. Same auth as
621
627
  // every other settings route; the brief itself is an ordinary durable job.
@@ -930,6 +936,7 @@ async function gracefulShutdown(): Promise<void> {
930
936
  const forceExit = setTimeout(() => process.exit(1), 8_000)
931
937
  forceExit.unref?.()
932
938
  stopMorningBriefScheduler()
939
+ await workReviewRuntime?.close()
933
940
  stopMeetingImportScheduler()
934
941
  stopMeetingMergeScheduler()
935
942
  sessionHooksRuntime.stop()
@@ -1176,6 +1183,7 @@ listenRequiredServers(listeners).then(() => {
1176
1183
  // that store is ready. Its own tick checks the durable-jobs switch and the
1177
1184
  // maintenance gate, so starting it here is safe when either is off.
1178
1185
  startMorningBriefScheduler()
1186
+ void workReviewRuntime?.start().catch(error => console.error('[work-reviews] reconciliation unavailable:', error))
1179
1187
  }).catch(error => {
1180
1188
  // The store remains degraded and rejects admission. Legacy /api/query is
1181
1189
  // still mounted, so the kill switch is an immediate rollback.