@gotcos/glasses-server 6.36.18 → 6.36.20
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
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
+
## 6.36.20
|
|
2
|
+
|
|
3
|
+
Follow-up to 6.36.19, which was never published. QA found three things in it.
|
|
4
|
+
|
|
5
|
+
**The reset CLI mutated production archives just by importing.**
|
|
6
|
+
`message-era-reset.ts` imported `endSession`/`getActiveSessions` from
|
|
7
|
+
`conversation.js` and used neither -- but that module's scope runs
|
|
8
|
+
`loadFromDisk()` and a boot `runDailyArchiveMirror()`. In the one-shot CLI that
|
|
9
|
+
is a second process concurrently loading and rewriting the archives the live
|
|
10
|
+
server owns, and `appendToArchive` appends rather than upserts, so it can
|
|
11
|
+
duplicate prior-day chats. The import is gone, and a test now asserts it stays
|
|
12
|
+
gone. The previous canary injected an `archiveAndRelease` and asserted it was
|
|
13
|
+
never called, which only ever caught a regression routed through the callback --
|
|
14
|
+
the code it replaced called `endSession()` directly as its default. `sessions`
|
|
15
|
+
and `archiveAndRelease` are deleted from the input type rather than left as a
|
|
16
|
+
silent no-op for a future caller.
|
|
17
|
+
|
|
18
|
+
**A corrupt `message-era.json` was indistinguishable from a reset.**
|
|
19
|
+
`currentMessageEraState` fell back to `legacy` on missing OR invalid content.
|
|
20
|
+
Reverting to `legacy` after a real era existed is the worst available answer: it
|
|
21
|
+
re-reads every era-stamped exchange as a PREVIOUS era, hiding it from
|
|
22
|
+
`/all-messages`, while promoting unstamped ones to current -- and to a client it
|
|
23
|
+
looks exactly like a reset nobody asked for. Missing still means `legacy`, which
|
|
24
|
+
is correct for a first upgrade whose exchanges carry no stamp at all. Corrupt
|
|
25
|
+
now rotates explicitly, says so on stderr, and degrades to `legacy` without
|
|
26
|
+
caching only if the replacement cannot be persisted.
|
|
27
|
+
|
|
28
|
+
**The header described a code path that had been removed.** It still said
|
|
29
|
+
rotation stops "if an archive write fails". There is no archive write. It now
|
|
30
|
+
also records what 6.36.19 quietly changed: this path does NOT archive, and the
|
|
31
|
+
daily mirror skips today by design, so a same-day copy exists only after an
|
|
32
|
+
explicit session end or `POST /api/archive/now`.
|
|
33
|
+
|
|
34
|
+
The 409 copy on both query paths no longer tells the wearer to reopen for a
|
|
35
|
+
"fresh message list" -- as of app 6.8.423 the cards stay.
|
|
36
|
+
|
|
37
|
+
Requires app 6.8.423. Suite 2985 / 211 files, tsc 0. The conversation-import
|
|
38
|
+
canary is mutation-verified: reintroducing the import fails it.
|
|
39
|
+
|
|
40
|
+
## 6.36.19
|
|
41
|
+
- **Resetting the spoken message count no longer ends the conversation.**
|
|
42
|
+
`resetLiveMessageEra` used to `endSession` every live session before rotating
|
|
43
|
+
the era, so "reset the message count" also emptied CHAT and killed the thread
|
|
44
|
+
the wearer was in the middle of. It now rotates the era and nothing else.
|
|
45
|
+
- Numbers stay unique because they were never bare ints: a number is
|
|
46
|
+
`{messageEra, globalMsgNum}`. The era rotates, the current-era ceiling
|
|
47
|
+
restarts at 0, and leftover cards keep their old era and their old number.
|
|
48
|
+
Lookup already prefers the current era (`message-ref.ts:231-234`) and the
|
|
49
|
+
counter is already era-scoped (`message-ref.ts:242-251`), so "message N" stays
|
|
50
|
+
unambiguous without a second ID scheme.
|
|
51
|
+
- `archived` stays in the response and is now always `0`. Callers read the field,
|
|
52
|
+
so it is kept — but copy that reports it must stop claiming sessions were
|
|
53
|
+
archived. The `archive_failed` 503 path is **gone**, not relocated: there is no
|
|
54
|
+
archive step left to fail.
|
|
55
|
+
- Tests inverted to match. A supplied `archiveAndRelease` is now asserted
|
|
56
|
+
*never to be called*, and a refusing one can no longer block the rotation.
|
|
57
|
+
Mutation-verified: reintroducing the release loop fails the suite.
|
|
58
|
+
|
|
59
|
+
**Ship gate:** do not POST `/api/message-era/reset` from any surface until app
|
|
60
|
+
6.8.422 is also live. Server-first plus the old companion still wipes local
|
|
61
|
+
messages, and `/sessions/today/all-messages` is era-filtered so recover cannot
|
|
62
|
+
bring the leftover cards back.
|
|
63
|
+
|
|
1
64
|
## 6.36.18
|
|
2
65
|
- **Meetings list now carries voice-assignment tags.** Each row includes
|
|
3
66
|
`voiceReview` from the sidecar head (`speakers[]`) plus whether a human
|
package/package.json
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// Rotate the short-number namespace. Live sessions are NOT ended and nothing
|
|
2
|
+
// is archived here: the companion keeps every card, so the reset is a numbering
|
|
3
|
+
// change rather than a history change.
|
|
4
|
+
//
|
|
5
|
+
// Do not rotate the era if a query is in flight.
|
|
6
|
+
//
|
|
7
|
+
// NOTE: this file deliberately imports nothing from ./conversation.js. Importing
|
|
8
|
+
// it runs its module scope -- loadFromDisk() and a boot runDailyArchiveMirror()
|
|
9
|
+
// -- which in the one-shot CLI means a second process concurrently loading and
|
|
10
|
+
// rewriting the same archives the live server owns. appendToArchive appends
|
|
11
|
+
// rather than upserts, so that duplicates prior-day chats.
|
|
12
|
+
//
|
|
13
|
+
// Today's exchanges are NOT archived by this path. The daily mirror skips
|
|
14
|
+
// today by design (conversation.ts: `sessionDay >= todayLocal`), so a same-day
|
|
15
|
+
// copy exists only after an explicit session end or POST /api/archive/now.
|
|
7
16
|
import {
|
|
8
17
|
createMessageEra,
|
|
9
18
|
currentMessageEraState,
|
|
10
19
|
type MessageEraState,
|
|
11
20
|
} from './message-era.js'
|
|
12
|
-
import type { SessionToArchive } from './archive.js'
|
|
13
21
|
|
|
14
22
|
export class MessageEraResetError extends Error {
|
|
15
23
|
readonly code: string
|
|
@@ -37,8 +45,6 @@ export interface MessageEraResetInput {
|
|
|
37
45
|
now?: number
|
|
38
46
|
activeRuns?: number
|
|
39
47
|
shuttingDown?: boolean
|
|
40
|
-
sessions?: SessionToArchive[]
|
|
41
|
-
archiveAndRelease?: (session: SessionToArchive) => Promise<boolean>
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
async function resolveJobHealth(input: MessageEraResetInput): Promise<{ activeRuns: number; shuttingDown: boolean }> {
|
|
@@ -79,26 +85,29 @@ export async function resetLiveMessageEra(input: MessageEraResetInput): Promise<
|
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
const previous = currentMessageEraState()
|
|
82
|
-
const sessions = input.sessions ?? getActiveSessions()
|
|
83
|
-
const archiveAndRelease = input.archiveAndRelease ?? (async (session: SessionToArchive) => {
|
|
84
|
-
const result = await endSession(session.id)
|
|
85
|
-
if (!result) return true
|
|
86
|
-
if (result.exchangeCount > 0 && !result.logged) return false
|
|
87
|
-
return true
|
|
88
|
-
})
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
89
|
+
// 6.36.19 — rotate the era, do NOT end the thread.
|
|
90
|
+
//
|
|
91
|
+
// This used to endSession() every live session before rotating, which is what
|
|
92
|
+
// made "reset the message count" also empty CHAT and kill the conversation the
|
|
93
|
+
// wearer was in the middle of. Miles wants the opposite shape: the next message
|
|
94
|
+
// is #1, the old cards keep their numbers, and the thread survives.
|
|
95
|
+
//
|
|
96
|
+
// Numbers stay unique because they are {messageEra, globalMsgNum}, not a bare
|
|
97
|
+
// int: the era rotates, the current-era ceiling restarts at 0, and leftover
|
|
98
|
+
// cards keep their old era. Lookup already prefers the current era
|
|
99
|
+
// (message-ref.ts:231-234) and the counter is already era-scoped
|
|
100
|
+
// (message-ref.ts:242-251), so nothing downstream needs to change to keep
|
|
101
|
+
// "message N" unambiguous.
|
|
102
|
+
//
|
|
103
|
+
// `archived` stays in the result and is now always 0. Control and the app read
|
|
104
|
+
// it, so removing the field would break them; the copy that reports it has to
|
|
105
|
+
// stop claiming sessions were archived.
|
|
106
|
+
//
|
|
107
|
+
// Deliberately no archive step here. There is nothing to release, so there is
|
|
108
|
+
// no 503 archive_failed path any more — that failure mode is gone rather than
|
|
109
|
+
// relocated. The day-archive mirror already retains history.
|
|
110
|
+
const archived = 0
|
|
102
111
|
|
|
103
112
|
const next: MessageEraState = createMessageEra(input.now ?? Date.now())
|
|
104
113
|
return {
|
|
@@ -55,9 +55,38 @@ export function currentMessageEraState(): MessageEraState {
|
|
|
55
55
|
cachedMtimeMs = mtimeMs
|
|
56
56
|
return cached
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
// Missing is not corrupt. A legacy install upgrading for the first time has
|
|
59
|
+
// no file AND no era stamps on its exchanges, and only LEGACY_MESSAGE_ERA
|
|
60
|
+
// classifies an unstamped exchange as current. Rotating here would file every
|
|
61
|
+
// one of them under a previous era and empty the chat on upgrade.
|
|
62
|
+
if (loaded.status === 'missing') {
|
|
63
|
+
cached = { v: 1, era: LEGACY_MESSAGE_ERA, startedAt: 0 }
|
|
64
|
+
cachedMtimeMs = mtimeMs
|
|
65
|
+
return cached
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Corrupt, or present-but-invalid: the file existed, so a real era almost
|
|
69
|
+
// certainly did too, and its value is now unrecoverable. Reverting to legacy
|
|
70
|
+
// would be the worst available answer -- it re-reads every era-stamped
|
|
71
|
+
// exchange as a PREVIOUS era (hiding it from /all-messages) while promoting
|
|
72
|
+
// unstamped ones to current, and it is indistinguishable from a reset nobody
|
|
73
|
+
// asked for. Rotate explicitly and say so, so the state is self-consistent
|
|
74
|
+
// and the event is diagnosable.
|
|
75
|
+
console.error(
|
|
76
|
+
`[message-era] ${MESSAGE_ERA_FILE} was unreadable (status=${loaded.status}); `
|
|
77
|
+
+ 'rotating to a fresh era. Older messages stay in day archives and remain '
|
|
78
|
+
+ 'reachable by session, but not by short number.',
|
|
79
|
+
)
|
|
80
|
+
try {
|
|
81
|
+
return createMessageEra()
|
|
82
|
+
} catch (err) {
|
|
83
|
+
// Read-only or full data dir: degrade to legacy rather than crash the
|
|
84
|
+
// server, but never cache it -- the next call retries the rotation.
|
|
85
|
+
console.error('[message-era] could not persist the replacement era:', err)
|
|
86
|
+
cached = null
|
|
87
|
+
cachedMtimeMs = Number.NaN
|
|
88
|
+
return { v: 1, era: LEGACY_MESSAGE_ERA, startedAt: 0 }
|
|
89
|
+
}
|
|
61
90
|
}
|
|
62
91
|
|
|
63
92
|
export function currentMessageEra(): string {
|
|
@@ -82,7 +82,7 @@ export async function preparePublicDurableQueryAdmission(raw: unknown): Promise<
|
|
|
82
82
|
throw new QueryJobAdmissionPreparationError(
|
|
83
83
|
409,
|
|
84
84
|
'message_era_mismatch',
|
|
85
|
-
'Reopen or update COS Glasses
|
|
85
|
+
'Reopen or update COS Glasses. Your cards stay; the next message is #1.',
|
|
86
86
|
)
|
|
87
87
|
}
|
|
88
88
|
try {
|
package/server/routes/query.ts
CHANGED
|
@@ -60,7 +60,7 @@ queryRouter.post('/query', async (req, res) => {
|
|
|
60
60
|
maintenanceLease.release()
|
|
61
61
|
return res.status(409).json({
|
|
62
62
|
error: 'message_era_mismatch',
|
|
63
|
-
detail: 'Reopen or update COS Glasses
|
|
63
|
+
detail: 'Reopen or update COS Glasses. Your cards stay; the next message is #1.',
|
|
64
64
|
era: activeMessageEra,
|
|
65
65
|
})
|
|
66
66
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// Creates a fresh short-number namespace. Live sessions are NOT ended and
|
|
3
|
+
// nothing is archived -- the companion keeps every card. Disk mtime is enough
|
|
4
|
+
// — no server restart.
|
|
4
5
|
//
|
|
5
6
|
// npx tsx server/scripts/reset-message-era.ts --confirm
|
|
6
7
|
|
|
@@ -22,7 +23,7 @@ console.log(JSON.stringify({
|
|
|
22
23
|
restartRequired: false,
|
|
23
24
|
verify: 'GET /api/message-counter should return { max: 0, era: "<era above>" }',
|
|
24
25
|
nextSteps: [
|
|
25
|
-
'Phone:
|
|
26
|
+
'Phone: reopen the companion — cards stay, the next message is #1',
|
|
26
27
|
'Send a test message — expect #1',
|
|
27
28
|
],
|
|
28
29
|
}, null, 2))
|