@astrosheep/square 0.3.80 → 0.3.82
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/dist/artifact.js +3 -3
- package/dist/catch-decisions.d.ts +1 -0
- package/dist/catch-decisions.js +8 -2
- package/dist/claude-mod.js +1 -2
- package/dist/cli/catch-presentation.js +3 -1
- package/dist/cli/context.js +1 -0
- package/dist/cli/express-presentation.js +2 -3
- package/dist/cli/maintenance-commands.js +2 -2
- package/dist/cli/registry.js +2 -1
- package/dist/cli/square-commands.d.ts +1 -0
- package/dist/cli/square-commands.js +7 -2
- package/dist/codex-hook.js +3 -11
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +3 -0
- package/dist/help.js +6 -5
- package/dist/hints.d.ts +4 -0
- package/dist/hints.js +25 -0
- package/dist/mcp.js +1 -1
- package/dist/model.d.ts +1 -1
- package/dist/participant-identity.d.ts +2 -0
- package/dist/participant-identity.js +13 -0
- package/dist/plugins/opencode/index.js +1 -3
- package/dist/presentation.d.ts +0 -1
- package/dist/presentation.js +3 -11
- package/dist/runtime.js +1 -1
- package/dist/session-lifecycle.d.ts +3 -0
- package/dist/session-lifecycle.js +21 -0
- package/dist/square-actions.d.ts +1 -0
- package/dist/square-actions.js +3 -2
- package/dist/square-api.js +7 -4
- package/dist/square-core.d.ts +5 -0
- package/dist/square-domain.d.ts +4 -0
- package/dist/square-domain.js +5 -1
- package/dist/square-facade.d.ts +6 -1
- package/dist/square-migration.d.ts +5 -7
- package/dist/square-migration.js +62 -42
- package/dist/square-sqlite.d.ts +7 -1
- package/dist/square-sqlite.js +52 -22
- package/dist/square-tools.js +2 -1
- package/dist/views.d.ts +1 -1
- package/dist/views.js +1 -1
- package/guides/claude-native.md +3 -3
- package/guides/migration.md +54 -57
- package/guides/row-storage-design.md +4 -1
- package/guides/square-api.md +26 -2
- package/package.json +2 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +6 -7
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/index.ts +1 -2
- package/plugins/pi/square-pi.js +4 -22
- package/skills/square/SKILL.md +6 -7
- package/dist/automatic-session.d.ts +0 -7
- package/dist/automatic-session.js +0 -137
package/dist/square-facade.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type ArchiveRecord = Readonly<StoredAct>;
|
|
|
7
7
|
export interface Activity {
|
|
8
8
|
readonly id: ActivityId;
|
|
9
9
|
readonly at: number;
|
|
10
|
-
readonly kind: 'join' | 'say' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore';
|
|
10
|
+
readonly kind: 'join' | 'say' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore' | 'unlisten';
|
|
11
11
|
readonly actor: string;
|
|
12
12
|
readonly body?: string;
|
|
13
13
|
readonly mentions: readonly string[];
|
|
@@ -40,6 +40,8 @@ export interface ExpressResult extends ActivityResult {
|
|
|
40
40
|
readonly replyAuthorNotHere?: string;
|
|
41
41
|
/** Body @name tokens with no standing match at landing; never stored on the activity. */
|
|
42
42
|
readonly unmatchedMentions?: readonly string[];
|
|
43
|
+
/** Occasional participant-facing nudge; not part of the activity. */
|
|
44
|
+
readonly hint?: string;
|
|
43
45
|
readonly observation?: ExpressObservation;
|
|
44
46
|
}
|
|
45
47
|
export interface ListenerChangeResult {
|
|
@@ -67,6 +69,8 @@ export interface CatchResult {
|
|
|
67
69
|
readonly idleExpired: boolean;
|
|
68
70
|
readonly remaining: number;
|
|
69
71
|
readonly status?: CatchTerminalStatus;
|
|
72
|
+
/** Occasional participant-facing nudge; not part of any activity. */
|
|
73
|
+
readonly hint?: string;
|
|
70
74
|
readonly observation?: CatchObservation;
|
|
71
75
|
}
|
|
72
76
|
export interface CatchWaitStart {
|
|
@@ -184,6 +188,7 @@ export interface Participant {
|
|
|
184
188
|
express(body: string, options?: ExpressOptions, control?: OperationControl): Promise<ExpressResult>;
|
|
185
189
|
listen(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
|
|
186
190
|
ignore(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
|
|
191
|
+
unlisten(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
|
|
187
192
|
listening(options?: ObservationOptions, control?: OperationControl): Promise<ListeningResult>;
|
|
188
193
|
catch(options?: CatchOptions, control?: OperationControl): Promise<CatchResult>;
|
|
189
194
|
history(query?: HistoryQuery, control?: OperationControl): Promise<HistoryResult>;
|
|
@@ -8,17 +8,15 @@ export interface MigrationCounts {
|
|
|
8
8
|
readonly routes: number;
|
|
9
9
|
}
|
|
10
10
|
export interface MigrationOutcome {
|
|
11
|
-
readonly status: 'migrated' | 'already-
|
|
11
|
+
readonly status: 'migrated' | 'already-current';
|
|
12
12
|
/** Participants standing after the conversion, for the CLI header. */
|
|
13
13
|
readonly standing: number;
|
|
14
14
|
readonly counts?: MigrationCounts;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Explicit in-place conversion of a
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* One transaction: preflight framing, the same recheck under the lock, the
|
|
22
|
-
* preserved authority, the derived facts, the format flip and a single COMMIT.
|
|
17
|
+
* Explicit in-place conversion of a version 1 snapshot or version 2 row
|
|
18
|
+
* artifact. Each source is validated before and under the write lock; v1 is
|
|
19
|
+
* imported through the normal current row initializer, while v2 rebuilds only
|
|
20
|
+
* activities and its indexes before publishing the new format version.
|
|
23
21
|
*/
|
|
24
22
|
export declare function migrateSquareFile(location: string, signal?: AbortSignal): Promise<MigrationOutcome>;
|
package/dist/square-migration.js
CHANGED
|
@@ -3,7 +3,7 @@ import { inSqliteTransaction } from './sqlite-transaction.js';
|
|
|
3
3
|
import { canonicalPath } from './canonical-path.js';
|
|
4
4
|
import { isWakeRouteKind } from './model.js';
|
|
5
5
|
import { parseActivityId } from './square-core.js';
|
|
6
|
-
import { APPLICATION_ID, V1_SNAPSHOT_TABLE, createSqliteDriver, initializeSqlite, invalidArtifact, validateLegacyFraming, validateSqliteHeader, validateStoredActivity, validateWatchLease, } from './square-sqlite.js';
|
|
6
|
+
import { APPLICATION_ID, ACTIVITIES_TABLE_SQL, ACTIVITY_INDEXES_SQL, CURRENT_USER_VERSION, V2_USER_VERSION, V1_SNAPSHOT_TABLE, createSqliteDriver, initializeSqlite, invalidArtifact, validateLegacyFraming, validateRowFraming, validateSqliteHeader, validateStoredActivity, validateWatchLease, } from './square-sqlite.js';
|
|
7
7
|
function isObject(value) {
|
|
8
8
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
9
|
}
|
|
@@ -57,7 +57,7 @@ function validateActs(value) {
|
|
|
57
57
|
try {
|
|
58
58
|
for (const candidate of value) {
|
|
59
59
|
const act = validateStoredActivity(candidate);
|
|
60
|
-
if (act.index <= previous)
|
|
60
|
+
if (act.kind === 'unlisten' || act.index <= previous)
|
|
61
61
|
return false;
|
|
62
62
|
previous = act.index;
|
|
63
63
|
}
|
|
@@ -153,58 +153,78 @@ function counts(state) {
|
|
|
153
153
|
routes: state.routes?.length ?? 0,
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
|
+
function rowCounts(database) {
|
|
157
|
+
const count = (table) => Number(database.prepare(`SELECT COUNT(*) AS n FROM ${table}`).get().n);
|
|
158
|
+
return { activities: count('activities'), receipts: count('receipts'), leases: count('catch_leases'), routes: count('routes') };
|
|
159
|
+
}
|
|
160
|
+
const ACTIVITY_INDEX_NAMES = ['activity_kind_index', 'activity_actor_index', 'activity_say_actor_index', 'activity_say_time', 'activity_bell_actor_time'];
|
|
161
|
+
function rebuildV2Activities(database) {
|
|
162
|
+
database.exec([
|
|
163
|
+
...ACTIVITY_INDEX_NAMES.map((name) => `DROP INDEX ${name};`),
|
|
164
|
+
'ALTER TABLE activities RENAME TO activities_v2;',
|
|
165
|
+
ACTIVITIES_TABLE_SQL,
|
|
166
|
+
'INSERT INTO activities(act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json) SELECT act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json FROM activities_v2 ORDER BY act_index;',
|
|
167
|
+
'DROP TABLE activities_v2;',
|
|
168
|
+
...ACTIVITY_INDEXES_SQL.map((sql) => `${sql};`),
|
|
169
|
+
].join('\n'));
|
|
170
|
+
}
|
|
171
|
+
function validateMigrationSource(database, location) {
|
|
172
|
+
const before = identity(database);
|
|
173
|
+
if (before.applicationId !== APPLICATION_ID)
|
|
174
|
+
throw invalidArtifact('unrelated SQLite database.');
|
|
175
|
+
if (before.userVersion === 1) {
|
|
176
|
+
validateLegacyFraming(database, APPLICATION_ID, location);
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
if (before.userVersion === V2_USER_VERSION) {
|
|
180
|
+
validateRowFraming(database, V2_USER_VERSION, location);
|
|
181
|
+
createSqliteDriver(database).header();
|
|
182
|
+
return 2;
|
|
183
|
+
}
|
|
184
|
+
if (before.userVersion === CURRENT_USER_VERSION) {
|
|
185
|
+
validateSqliteHeader(database, location);
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
throw invalidArtifact('unsupported SQLite format version.');
|
|
189
|
+
}
|
|
156
190
|
/**
|
|
157
|
-
* Explicit in-place conversion of a
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* One transaction: preflight framing, the same recheck under the lock, the
|
|
162
|
-
* preserved authority, the derived facts, the format flip and a single COMMIT.
|
|
191
|
+
* Explicit in-place conversion of a version 1 snapshot or version 2 row
|
|
192
|
+
* artifact. Each source is validated before and under the write lock; v1 is
|
|
193
|
+
* imported through the normal current row initializer, while v2 rebuilds only
|
|
194
|
+
* activities and its indexes before publishing the new format version.
|
|
163
195
|
*/
|
|
164
196
|
export async function migrateSquareFile(location, signal) {
|
|
165
197
|
const squarePath = await canonicalPath(location);
|
|
166
198
|
const database = openExistingSquareDatabase(squarePath);
|
|
167
199
|
try {
|
|
168
|
-
|
|
169
|
-
// Every preflight statement reads one snapshot taken in the helper's
|
|
170
|
-
// retryable stage: a transient writer lock is waited out like any other
|
|
171
|
-
// acquisition, and a migrator that commits while we look cannot move the
|
|
172
|
-
// artifact between the identity read and the schema reads.
|
|
173
|
-
let alreadyConverted;
|
|
200
|
+
let alreadyCurrent;
|
|
174
201
|
const preflight = await inSqliteTransaction(database, false, () => {
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
validateSqliteHeader(database, squarePath);
|
|
183
|
-
alreadyConverted = { status: 'already-format-2', standing: standing(database) };
|
|
184
|
-
}, () => alreadyConverted, signal);
|
|
185
|
-
if (preflight !== undefined)
|
|
186
|
-
return preflight;
|
|
202
|
+
const source = validateMigrationSource(database, squarePath);
|
|
203
|
+
if (source === undefined)
|
|
204
|
+
alreadyCurrent = { status: 'already-current', standing: standing(database) };
|
|
205
|
+
return source;
|
|
206
|
+
}, (source) => source, signal);
|
|
207
|
+
if (preflight === undefined)
|
|
208
|
+
return alreadyCurrent;
|
|
187
209
|
database.exec('PRAGMA busy_timeout = 0; PRAGMA synchronous = FULL');
|
|
188
210
|
const result = {};
|
|
189
211
|
await inSqliteTransaction(database, true, () => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (locked.applicationId !== APPLICATION_ID || locked.userVersion !== 1) {
|
|
193
|
-
// A valid row artifact is done already; anything else is refused here.
|
|
194
|
-
validateSqliteHeader(database, squarePath);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
validateLegacyFraming(database, APPLICATION_ID, squarePath);
|
|
198
|
-
result.converted = true;
|
|
212
|
+
const source = validateMigrationSource(database, squarePath);
|
|
213
|
+
result.source = source;
|
|
199
214
|
}, () => {
|
|
200
|
-
if (result.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
215
|
+
if (result.source === 1) {
|
|
216
|
+
const legacy = decodeLegacySnapshot(database);
|
|
217
|
+
initializeSqlite(database, legacy.state, legacy.revision);
|
|
218
|
+
database.exec(`DROP TABLE ${V1_SNAPSHOT_TABLE}; PRAGMA user_version = ${CURRENT_USER_VERSION}`);
|
|
219
|
+
result.outcome = { status: 'migrated', standing: standing(database), counts: counts(legacy.state) };
|
|
220
|
+
}
|
|
221
|
+
else if (result.source === 2) {
|
|
222
|
+
rebuildV2Activities(database);
|
|
223
|
+
database.exec(`PRAGMA user_version = ${CURRENT_USER_VERSION}`);
|
|
224
|
+
result.outcome = { status: 'migrated', standing: standing(database), counts: rowCounts(database) };
|
|
225
|
+
}
|
|
206
226
|
}, signal);
|
|
207
|
-
return result.outcome ?? { status: 'already-
|
|
227
|
+
return result.outcome ?? { status: 'already-current', standing: standing(database) };
|
|
208
228
|
}
|
|
209
229
|
finally {
|
|
210
230
|
database.close();
|
package/dist/square-sqlite.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface SquareState {
|
|
|
19
19
|
}
|
|
20
20
|
export declare function emptyRuntimeState(nextActIndex?: number): SquareRuntimeState;
|
|
21
21
|
export declare const APPLICATION_ID = 1397838405;
|
|
22
|
+
export declare const V2_USER_VERSION = 2;
|
|
23
|
+
export declare const CURRENT_USER_VERSION = 3;
|
|
22
24
|
export declare const V1_SNAPSHOT_TABLE = "square_snapshot";
|
|
23
25
|
export interface LegacyFraming {
|
|
24
26
|
readonly applicationId: number;
|
|
@@ -32,11 +34,15 @@ export interface LegacyFraming {
|
|
|
32
34
|
* migration. Anything else is unrelated or unsupported, not repaired.
|
|
33
35
|
*/
|
|
34
36
|
export declare function validateLegacyFraming(database: DatabaseSync, applicationId: number, location?: string): LegacyFraming;
|
|
35
|
-
export declare const
|
|
37
|
+
export declare const ACTIVITIES_TABLE_SQL = "CREATE TABLE activities (\n act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),\n at REAL NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')),\n actor_json TEXT COLLATE BINARY,\n actor_key_json TEXT COLLATE BINARY,\n reach TEXT CHECK (reach = 'bell'),\n reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),\n CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),\n CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))\n);";
|
|
38
|
+
export declare const ACTIVITY_INDEXES_SQL: readonly ["CREATE INDEX activity_kind_index ON activities(kind, act_index)", "CREATE INDEX activity_actor_index ON activities(actor_key_json, act_index)", "CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say'", "CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say'", "CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell'"];
|
|
39
|
+
export declare const SCHEMA_SQL: string;
|
|
40
|
+
export declare const SCHEMA_SQL_V2: string;
|
|
36
41
|
export declare function invalidArtifact(detail: string): SquareError;
|
|
37
42
|
export declare function validateStoredActivity(value: unknown): StoredAct;
|
|
38
43
|
export declare function validateWatchLease(value: unknown): WatchLease;
|
|
39
44
|
export declare function validateRoute(value: unknown): RouteFact;
|
|
45
|
+
export declare function validateRowFraming(db: DatabaseSync, version: 2 | 3, location?: string): void;
|
|
40
46
|
export declare function validateSqliteHeader(db: DatabaseSync, location?: string): SquareHeader;
|
|
41
47
|
export interface SqliteDriver extends SquareRead, LandingWriter {
|
|
42
48
|
readonly changed: boolean;
|
package/dist/square-sqlite.js
CHANGED
|
@@ -6,6 +6,8 @@ export function emptyRuntimeState(nextActIndex = 0) {
|
|
|
6
6
|
}
|
|
7
7
|
export const APPLICATION_ID = 0x53515245;
|
|
8
8
|
const V1_USER_VERSION = 1;
|
|
9
|
+
export const V2_USER_VERSION = 2;
|
|
10
|
+
export const CURRENT_USER_VERSION = 3;
|
|
9
11
|
export const V1_SNAPSHOT_TABLE = 'square_snapshot';
|
|
10
12
|
const V1_SNAPSHOT_DDL = `CREATE TABLE ${V1_SNAPSHOT_TABLE} (id INTEGER PRIMARY KEY CHECK(id=1), revision INTEGER NOT NULL CHECK(revision>=0), state TEXT NOT NULL)`;
|
|
11
13
|
const V1_SNAPSHOT_COLUMNS = [
|
|
@@ -50,19 +52,12 @@ export function validateLegacyFraming(database, applicationId, location = '') {
|
|
|
50
52
|
function normalizedLegacySql(sql) {
|
|
51
53
|
return sql.trim().replace(/\s+/g, ' ').replace(/\s*(>=|[(),=])\s*/g, '$1');
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
next_act_index INTEGER NOT NULL CHECK (next_act_index BETWEEN 0 AND 9007199254740991),
|
|
57
|
-
hard_cap INTEGER CHECK (hard_cap BETWEEN 1 AND 9007199254740991),
|
|
58
|
-
throttle_per_minute INTEGER CHECK (throttle_per_minute BETWEEN 1 AND 9007199254740991),
|
|
59
|
-
preamble_json TEXT NOT NULL CHECK (json_valid(preamble_json)),
|
|
60
|
-
warmup_json TEXT NOT NULL CHECK (json_valid(warmup_json))
|
|
61
|
-
);
|
|
62
|
-
CREATE TABLE activities (
|
|
55
|
+
const V2_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','hold','resume','read','listen','ignore')";
|
|
56
|
+
const CURRENT_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')";
|
|
57
|
+
export const ACTIVITIES_TABLE_SQL = `CREATE TABLE activities (
|
|
63
58
|
act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
64
59
|
at REAL NOT NULL,
|
|
65
|
-
kind TEXT NOT NULL CHECK (
|
|
60
|
+
kind TEXT NOT NULL CHECK (${CURRENT_ACTIVITY_KIND_CHECK}),
|
|
66
61
|
actor_json TEXT COLLATE BINARY,
|
|
67
62
|
actor_key_json TEXT COLLATE BINARY,
|
|
68
63
|
reach TEXT CHECK (reach = 'bell'),
|
|
@@ -71,12 +66,25 @@ CREATE TABLE activities (
|
|
|
71
66
|
CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),
|
|
72
67
|
CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),
|
|
73
68
|
CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))
|
|
69
|
+
);`;
|
|
70
|
+
export const ACTIVITY_INDEXES_SQL = [
|
|
71
|
+
'CREATE INDEX activity_kind_index ON activities(kind, act_index)',
|
|
72
|
+
'CREATE INDEX activity_actor_index ON activities(actor_key_json, act_index)',
|
|
73
|
+
"CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say'",
|
|
74
|
+
"CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say'",
|
|
75
|
+
"CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell'",
|
|
76
|
+
];
|
|
77
|
+
export const SCHEMA_SQL = `CREATE TABLE square_meta (
|
|
78
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
79
|
+
revision INTEGER NOT NULL CHECK (revision BETWEEN 0 AND 9007199254740991),
|
|
80
|
+
next_act_index INTEGER NOT NULL CHECK (next_act_index BETWEEN 0 AND 9007199254740991),
|
|
81
|
+
hard_cap INTEGER CHECK (hard_cap BETWEEN 1 AND 9007199254740991),
|
|
82
|
+
throttle_per_minute INTEGER CHECK (throttle_per_minute BETWEEN 1 AND 9007199254740991),
|
|
83
|
+
preamble_json TEXT NOT NULL CHECK (json_valid(preamble_json)),
|
|
84
|
+
warmup_json TEXT NOT NULL CHECK (json_valid(warmup_json))
|
|
74
85
|
);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say';
|
|
78
|
-
CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say';
|
|
79
|
-
CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell';
|
|
86
|
+
${ACTIVITIES_TABLE_SQL}
|
|
87
|
+
${ACTIVITY_INDEXES_SQL.join(';\n')};
|
|
80
88
|
|
|
81
89
|
CREATE TABLE receipts (
|
|
82
90
|
owner_json TEXT COLLATE BINARY NOT NULL,
|
|
@@ -134,6 +142,8 @@ CREATE TABLE landed_audience (
|
|
|
134
142
|
) WITHOUT ROWID;
|
|
135
143
|
CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);`;
|
|
136
144
|
const EXPECTED_SCHEMA_SQL = SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
145
|
+
export const SCHEMA_SQL_V2 = SCHEMA_SQL.replace(CURRENT_ACTIVITY_KIND_CHECK, V2_ACTIVITY_KIND_CHECK);
|
|
146
|
+
const EXPECTED_SCHEMA_SQL_V2 = SCHEMA_SQL_V2.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
137
147
|
const encode = JSON.stringify;
|
|
138
148
|
const key = (name) => encode(nameKey(name));
|
|
139
149
|
export function invalidArtifact(detail) {
|
|
@@ -189,7 +199,7 @@ function immutable(value) {
|
|
|
189
199
|
}
|
|
190
200
|
return value;
|
|
191
201
|
}
|
|
192
|
-
const kinds = ['join', 'done', 'say', 'hold', 'resume', 'read', 'listen', 'ignore'];
|
|
202
|
+
const kinds = ['join', 'done', 'say', 'hold', 'resume', 'read', 'listen', 'ignore', 'unlisten'];
|
|
193
203
|
function head(row) {
|
|
194
204
|
const index = integer(row.act_index), at = finite(row.at);
|
|
195
205
|
requireFact(kinds.includes(string(row.kind)));
|
|
@@ -237,6 +247,7 @@ export function validateStoredActivity(value) {
|
|
|
237
247
|
break;
|
|
238
248
|
case 'listen':
|
|
239
249
|
case 'ignore':
|
|
250
|
+
case 'unlisten':
|
|
240
251
|
shape(a, [...common, 'target']);
|
|
241
252
|
string(a.target, true);
|
|
242
253
|
break;
|
|
@@ -247,7 +258,7 @@ export function validateStoredActivity(value) {
|
|
|
247
258
|
function activity(row) {
|
|
248
259
|
const h = head(row), payload = object(json(row.payload_json));
|
|
249
260
|
// Payload cannot shadow indexed structural columns.
|
|
250
|
-
const allowed = h.kind === 'say' ? ['body', 'mentions'] : h.kind === 'done' || h.kind === 'hold' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' ? ['target'] : [];
|
|
261
|
+
const allowed = h.kind === 'say' ? ['body', 'mentions'] : h.kind === 'done' || h.kind === 'hold' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
|
|
251
262
|
requireFact(Object.keys(payload).every(k => allowed.includes(k)));
|
|
252
263
|
return immutable(validateStoredActivity({ ...h, ...payload }));
|
|
253
264
|
}
|
|
@@ -315,6 +326,17 @@ function route(row) {
|
|
|
315
326
|
}));
|
|
316
327
|
}
|
|
317
328
|
function normalizedSql(sql) { return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1'); }
|
|
329
|
+
function validateOwnedSchema(db, expected, location) {
|
|
330
|
+
const actual = db.prepare("SELECT sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' AND sql IS NOT NULL").all().map(r => normalizedSql(string(r.sql))).sort();
|
|
331
|
+
requireFact(actual.length === expected.length && actual.every((sql, i) => sql === expected[i]), 'unsupported row schema.');
|
|
332
|
+
requireFact(db.prepare('PRAGMA journal_mode').get().journal_mode === (location === ':memory:' ? 'memory' : 'delete'), 'unsupported journal mode.');
|
|
333
|
+
}
|
|
334
|
+
export function validateRowFraming(db, version, location = '') {
|
|
335
|
+
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
336
|
+
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
337
|
+
requireFact(identity.user_version === version, 'unsupported SQLite format version.');
|
|
338
|
+
validateOwnedSchema(db, version === V2_USER_VERSION ? EXPECTED_SCHEMA_SQL_V2 : EXPECTED_SCHEMA_SQL, location);
|
|
339
|
+
}
|
|
318
340
|
export function validateSqliteHeader(db, location = '') {
|
|
319
341
|
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
320
342
|
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
@@ -322,10 +344,12 @@ export function validateSqliteHeader(db, location = '') {
|
|
|
322
344
|
validateLegacyFraming(db, APPLICATION_ID, location);
|
|
323
345
|
throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
|
|
324
346
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
347
|
+
if (identity.user_version === V2_USER_VERSION) {
|
|
348
|
+
validateRowFraming(db, V2_USER_VERSION, location);
|
|
349
|
+
throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
|
|
350
|
+
}
|
|
351
|
+
requireFact(identity.user_version === CURRENT_USER_VERSION, 'unsupported SQLite format version.');
|
|
352
|
+
validateRowFraming(db, CURRENT_USER_VERSION, location);
|
|
329
353
|
return createSqliteDriver(db).header();
|
|
330
354
|
}
|
|
331
355
|
function activitySelection(filter, alias = 'a') {
|
|
@@ -550,6 +574,12 @@ export function createSqliteDriver(db) {
|
|
|
550
574
|
}
|
|
551
575
|
if (effect.removeListeningOf !== undefined)
|
|
552
576
|
run("DELETE FROM attention WHERE listener_key_json=? AND kind='listen'", [key(effect.removeListeningOf)]);
|
|
577
|
+
if (effect.removeListening !== undefined) {
|
|
578
|
+
const target = effect.removeListening;
|
|
579
|
+
string(target.listener, true);
|
|
580
|
+
string(target.target, true);
|
|
581
|
+
run("DELETE FROM attention WHERE listener_key_json=? AND target_key_json=? AND kind='listen'", [key(target.listener), key(target.target)]);
|
|
582
|
+
}
|
|
553
583
|
if (effect.replaceAttention) {
|
|
554
584
|
const t = effect.replaceAttention;
|
|
555
585
|
string(t.listener, true);
|
package/dist/square-tools.js
CHANGED
|
@@ -34,8 +34,9 @@ export const SQUARE_TOOLS = [
|
|
|
34
34
|
limit: z.number().int().min(1).optional(), order: z.enum(['asc', 'desc']).optional(), before: activityId.optional(), after: activityId.optional(),
|
|
35
35
|
grep: z.string().optional(), fixed: z.string().optional(), from: stringArray.optional(), mention: z.string().optional(),
|
|
36
36
|
}, (square, args, control) => square.history(args, control)),
|
|
37
|
-
tool('listen', 'Listen for future
|
|
37
|
+
tool('listen', 'Listen for future activity from a participant, clearing any ignore of them.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).listen(target, {}, control)),
|
|
38
38
|
tool('ignore', 'Block a participant’s future mentions and bare activity. Listening again clears the ignore; already landed activity is unchanged.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).ignore(target, {}, control)),
|
|
39
|
+
tool('unlisten', 'Stop listening to a participant’s future activity; mentions and replies still reach you. An existing ignore is unchanged.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).unlisten(target, {}, control)),
|
|
39
40
|
tool('listening', 'List participants this participant is listening to.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).listening({}, control)),
|
|
40
41
|
tool('hold', 'Raise a hand to hold the square.', { as: participant, reason: z.string().optional() }, async (square, { as, reason }, control) => (await square.participant(as)).hold(reason, {}, control)),
|
|
41
42
|
tool('resume', 'Release the current hold.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).resume({}, control)),
|
package/dist/views.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { Activity, ArchiveRecord, HistoryQuery, HistoryContinuation, Operat
|
|
|
6
6
|
export type { EntryPresentation, ListPresentation, CatchPresence, StreamProjection } from './square-facade.js';
|
|
7
7
|
export declare const HISTORY_DEFAULT_LIMIT = 10;
|
|
8
8
|
export declare const HISTORY_MAX_LIMIT = 100;
|
|
9
|
-
export declare const NON_READ: readonly ["join", "done", "say", "hold", "resume", "listen", "ignore"];
|
|
9
|
+
export declare const NON_READ: readonly ["join", "done", "say", "hold", "resume", "listen", "ignore", "unlisten"];
|
|
10
10
|
/** The one StoredAct-to-public-Activity field mapping. Rejects read and actorless acts. */
|
|
11
11
|
export declare function toPublicActivity(stored: StoredAct): Activity;
|
|
12
12
|
export declare function parseRequiredActivityId(id: ActivityId): number;
|
package/dist/views.js
CHANGED
|
@@ -9,7 +9,7 @@ export const HISTORY_DEFAULT_LIMIT = 10;
|
|
|
9
9
|
export const HISTORY_MAX_LIMIT = 100;
|
|
10
10
|
const STATUS_HERE_WINDOW_MS = 30 * 60_000;
|
|
11
11
|
const PRESENCE_WINDOW_MS = 8 * 60 * 60_000;
|
|
12
|
-
export const NON_READ = ['join', 'done', 'say', 'hold', 'resume', 'listen', 'ignore'];
|
|
12
|
+
export const NON_READ = ['join', 'done', 'say', 'hold', 'resume', 'listen', 'ignore', 'unlisten'];
|
|
13
13
|
const ARCHIVE = new Set(['join', 'done', 'say', 'hold', 'resume']);
|
|
14
14
|
/** The one StoredAct-to-public-Activity field mapping. Rejects read and actorless acts. */
|
|
15
15
|
export function toPublicActivity(stored) {
|
package/guides/claude-native.md
CHANGED
|
@@ -50,9 +50,9 @@ protocol, resident service or ledger schema migration is introduced.
|
|
|
50
50
|
|
|
51
51
|
## Membership and lifecycle
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
Participants join explicitly. The mod never discovers or joins a square from its
|
|
54
|
+
filename; `PUBLIC.square` has no special behavior. Existing memberships reconcile
|
|
55
|
+
on startup. Main-conversation tool completion (including failed/aborted tools) and
|
|
56
56
|
conversation start reconcile late memberships: the receiver itself publishes
|
|
57
57
|
routes; the CLI cannot infer mod capability from a socket or session environment.
|
|
58
58
|
This reconciliation injects no activity and does not poll. Its recovery sweep
|
package/guides/migration.md
CHANGED
|
@@ -1,78 +1,75 @@
|
|
|
1
|
-
# Migrating an existing square
|
|
1
|
+
# Migrating an existing square
|
|
2
2
|
|
|
3
3
|
Square stores authority in one versioned SQLite artifact. Version 1 kept the
|
|
4
|
-
whole history as one JSON snapshot
|
|
5
|
-
|
|
6
|
-
facts — in one in-place database.
|
|
4
|
+
whole history as one JSON snapshot. Version 2 introduced typed rows. Current
|
|
5
|
+
format 3 adds the `unlisten` history event without changing the other tables.
|
|
7
6
|
|
|
8
|
-
Existing artifacts are **not** converted automatically.
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Existing artifacts are **not** converted automatically. Ordinary reads and
|
|
8
|
+
writes refuse versions 1 and 2 with an offline-migration requirement. The CLI
|
|
9
|
+
adds the concrete recovery command; native tools retain the description:
|
|
11
10
|
|
|
12
11
|
```sh
|
|
13
12
|
square --location /path/to/SQUARE.square migrate
|
|
14
13
|
```
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
`--force`, bulk mode, sidecar, daemon or dual reader
|
|
18
|
-
|
|
15
|
+
This is the only artifact migration command. There is no `--check`, `--backup`,
|
|
16
|
+
`--force`, bulk mode, sidecar, daemon or dual reader. The host ledger is a
|
|
17
|
+
separate resource; unlisten does not change its format.
|
|
19
18
|
|
|
20
19
|
## What the command does
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
1.
|
|
25
|
-
format
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
and
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
21
|
+
Both supported conversions happen **in place**, in **one transaction**:
|
|
22
|
+
|
|
23
|
+
1. Read the owned framing before any persistent pragma or DDL: application id,
|
|
24
|
+
source format, `DELETE` journal mode, and exact known schema.
|
|
25
|
+
2. Take `BEGIN IMMEDIATE` and re-check that framing under the lock.
|
|
26
|
+
3. Convert the source using its explicit path:
|
|
27
|
+
- **Version 1:** decode and validate the complete snapshot, create current
|
|
28
|
+
rows, and preserve activity indices/gaps, optional fields, raw receipt and
|
|
29
|
+
lease owners, ordered routes, strings and finite fractional timestamps.
|
|
30
|
+
Rebuild derived participant, hold, attention and landed-audience facts
|
|
31
|
+
with the runtime's shared landing transition, then drop the snapshot.
|
|
32
|
+
- **Version 2:** rebuild only `activities` and its indexes to accept
|
|
33
|
+
`unlisten`, copying every existing activity row unchanged. All other
|
|
34
|
+
tables, including attention, landed audience, receipts, leases, routes
|
|
35
|
+
and configuration, remain untouched. No history is reinterpreted.
|
|
36
|
+
4. Set format 3 and commit once.
|
|
37
|
+
|
|
38
|
+
Revision and assigned activity indices are preserved. Migration adds no domain
|
|
39
|
+
activity. Nothing is published piecemeal, and path, inode and registry identity
|
|
40
|
+
remain unchanged. A format-3 artifact is reported as already current without
|
|
41
|
+
writing, including when another migrator finished while this one waited.
|
|
43
42
|
|
|
44
43
|
## What it refuses
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Journal mode is never rewritten to make a migration possible.
|
|
45
|
+
Missing files, unrelated databases, unsupported versions, different journal
|
|
46
|
+
modes, unknown schemas and malformed headers are refused without a write.
|
|
47
|
+
Version-1 snapshot JSON and activity vocabulary must be valid for that source
|
|
48
|
+
format. Non-finite numbers anywhere in a legacy route, including nested extra
|
|
49
|
+
properties, are refused with the offending property named; they are not
|
|
50
|
+
silently coerced to null. Journal mode is never rewritten to make conversion
|
|
51
|
+
possible, and migration is not a corruption-repair command.
|
|
55
52
|
|
|
56
53
|
## Before you migrate
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- An offline backup is optional and, if you want one, ordinary: copy the file
|
|
63
|
-
while no writer is attached. The command does not manage backups.
|
|
55
|
+
Stop every session and service using that artifact. Make an offline backup, or
|
|
56
|
+
use SQLite's backup facilities rather than copying a live database. The command
|
|
57
|
+
does not manage backups, stop processes, revoke in-flight remote deliveries,
|
|
58
|
+
or update installed runtimes.
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
Upgrade all attached runtimes before reopening the migrated artifact. Both
|
|
61
|
+
version-1 and version-2 runtimes refuse format 3; do not mix old and new
|
|
62
|
+
processes against the converted file. An older writer either commits before
|
|
63
|
+
conversion or meets the new version gate afterwards.
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
after it, the complete version 2 rows are there. A process killed mid-migration
|
|
69
|
-
therefore leaves one or the other, never a partial artifact, and re-running the
|
|
70
|
-
command finishes the job. A busy `COMMIT` retries the same transaction and never
|
|
71
|
-
re-runs the conversion.
|
|
65
|
+
## Crash safety and checking
|
|
72
66
|
|
|
73
|
-
|
|
67
|
+
Before `COMMIT`, SQLite's journal recovers the complete source version; after
|
|
68
|
+
it, complete format-3 rows are present. A failure or process death cannot leave
|
|
69
|
+
a partial conversion. A busy `COMMIT` retries the same transaction, never the
|
|
70
|
+
copy or domain callback.
|
|
74
71
|
|
|
75
|
-
`square --location <path> doctor` is read-only. On
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
`square --location <path> doctor` is read-only. On versions 1 and 2 it prints
|
|
73
|
+
the migration requirement without calling the artifact damaged. On current
|
|
74
|
+
format 3 it validates rows and recomputes derived indices for comparison.
|
|
75
|
+
Run it after migration and before restarting upgraded clients.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Row-oriented artifact design
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Historical accepted design for artifact format 2.** The current runtime uses format 3 for the
|
|
4
|
+
`unlisten` event; see [Square API](square-api.md#artifact-format-and-explicit-migration) for current
|
|
5
|
+
format/migration behavior. The schema and delivery-stage terms below record the original row-storage
|
|
6
|
+
replacement, not a pending implementation or a second current-format definition.
|
|
4
7
|
Source baseline: `99679eb` (0.3.70). This document specifies the design; [runtime delivery evidence](row-runtime-delivery-evidence.md) records implementation verification and actual capacity measurements. Explicit migration remains a separately accepted delivery.
|
|
5
8
|
|
|
6
9
|
## Decision in brief
|