@c956180462/awbs 0.0.1

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 (50) hide show
  1. package/AWBS_CORE_DESIGN.md +983 -0
  2. package/AWBS_CURRENT_FEATURES.md +463 -0
  3. package/LICENSE +21 -0
  4. package/README.md +265 -0
  5. package/TASK_001_VIEW_AUTHORITY.md +446 -0
  6. package/TASK_003_AUTHORITY_LEDGER_AND_DB_AUDIT.md +268 -0
  7. package/TASK_004_TRUSTED_AUTHORITY_LAYER.md +547 -0
  8. package/TASK_005_AUTHORITY_SESSION.md +218 -0
  9. package/TASK_006_TRUST_BOUNDARY_HARDENING.md +381 -0
  10. package/TASK_007_TRUSTED_OPERATION_ENTRY.md +129 -0
  11. package/bin/awbs.js +2 -0
  12. package/docs/DEVELOPMENT_LEARNING.md +319 -0
  13. package/docs/FULL_CHAIN.md +295 -0
  14. package/docs/PRODUCT.md +188 -0
  15. package/docs/USAGE.md +294 -0
  16. package/package.json +45 -0
  17. package/src/adapters/file-summary-store.ts +88 -0
  18. package/src/adapters/git-cli.ts +107 -0
  19. package/src/adapters/local-authority-session.ts +606 -0
  20. package/src/adapters/local-file-database.ts +199 -0
  21. package/src/adapters/sealed-authority.ts +725 -0
  22. package/src/adapters/session-authority-client.ts +176 -0
  23. package/src/adapters/sqlite-index-store.ts +176 -0
  24. package/src/cli.ts +491 -0
  25. package/src/domain/authority-types.ts +194 -0
  26. package/src/domain/constants.ts +11 -0
  27. package/src/domain/errors.ts +6 -0
  28. package/src/domain/hash.ts +27 -0
  29. package/src/domain/path-policy.ts +36 -0
  30. package/src/domain/paths.ts +65 -0
  31. package/src/domain/session-proof.ts +140 -0
  32. package/src/domain/session-types.ts +101 -0
  33. package/src/domain/types.ts +94 -0
  34. package/src/ports/authority-session.ts +8 -0
  35. package/src/ports/authority.ts +26 -0
  36. package/src/ports/file-database.ts +18 -0
  37. package/src/ports/git.ts +23 -0
  38. package/src/ports/index-store.ts +7 -0
  39. package/src/ports/summary-store.ts +16 -0
  40. package/src/runtime.ts +56 -0
  41. package/src/session-entry.ts +1 -0
  42. package/src/usecases/authority.ts +53 -0
  43. package/src/usecases/changeset.ts +437 -0
  44. package/src/usecases/db.ts +192 -0
  45. package/src/usecases/index.ts +136 -0
  46. package/src/usecases/init.ts +48 -0
  47. package/src/usecases/ledger.ts +146 -0
  48. package/src/usecases/session.ts +48 -0
  49. package/src/usecases/trusted-chain.ts +56 -0
  50. package/src/usecases/view.ts +166 -0
@@ -0,0 +1,176 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { AwbsError } from "../domain/errors.ts";
4
+ import { attachControllerProof, verifyControllerResponseProof } from "../domain/session-proof.ts";
5
+ import type {
6
+ AuthorityCatalog,
7
+ AuthorityChangesetApplyOperation,
8
+ AuthorityChangesetReceipt,
9
+ AuthorityLedger,
10
+ AuthorityLedgerEntry,
11
+ AuthorityRepairReport,
12
+ AuthorityVerifyReport,
13
+ AuthorityViewContract
14
+ } from "../domain/authority-types.ts";
15
+ import type { AuthoritySessionRequest, AuthoritySessionResponse } from "../domain/session-types.ts";
16
+ import type { AuthorityPort } from "../ports/authority.ts";
17
+ import type { FileDatabasePort } from "../ports/file-database.ts";
18
+ import { SealedAuthorityAdapter } from "./sealed-authority.ts";
19
+
20
+ export class SessionAuthorityClientAdapter implements AuthorityPort {
21
+ private readonly cliPath: string;
22
+ private readonly controllerToken: string | null;
23
+
24
+ constructor(cliPath: string, options: { controllerToken?: string } = {}) {
25
+ this.cliPath = cliPath;
26
+ this.controllerToken = options.controllerToken ?? null;
27
+ }
28
+
29
+ ensureInitialized(root: string): void {
30
+ this.request(root, "ensureInitialized");
31
+ }
32
+
33
+ createView(root: string, contract: AuthorityViewContract): AuthorityViewContract {
34
+ return this.request<AuthorityViewContract>(root, "createView", [contract]);
35
+ }
36
+
37
+ getViewContract(root: string, viewId: string, options?: { allowRevoked?: boolean }): AuthorityViewContract {
38
+ return this.request<AuthorityViewContract>(root, "getViewContract", [viewId, options]);
39
+ }
40
+
41
+ revokeView(root: string, viewId: string): AuthorityViewContract {
42
+ return this.request<AuthorityViewContract>(root, "revokeView", [viewId]);
43
+ }
44
+
45
+ verify(root: string): AuthorityVerifyReport {
46
+ return this.request<AuthorityVerifyReport>(root, "verify");
47
+ }
48
+
49
+ repairMirrors(root: string): AuthorityRepairReport {
50
+ return this.request<AuthorityRepairReport>(root, "repairMirrors");
51
+ }
52
+
53
+ readCatalog(root: string): AuthorityCatalog {
54
+ return this.request<AuthorityCatalog>(root, "readCatalog");
55
+ }
56
+
57
+ hasLedger(root: string): boolean {
58
+ return this.request<boolean>(root, "hasLedger");
59
+ }
60
+
61
+ bootstrapLedger(root: string, parentTrustedCommit: string): AuthorityLedger {
62
+ return this.request<AuthorityLedger>(root, "bootstrapLedger", [parentTrustedCommit]);
63
+ }
64
+
65
+ readLedger(root: string): AuthorityLedger {
66
+ return this.request<AuthorityLedger>(root, "readLedger");
67
+ }
68
+
69
+ recordChangesetApply(root: string, operation: AuthorityChangesetApplyOperation): AuthorityLedgerEntry {
70
+ return this.request<AuthorityLedgerEntry>(root, "recordChangesetApply", [operation]);
71
+ }
72
+
73
+ sealChangesetReceipt(root: string, changesetRoot: string, receipt: AuthorityChangesetReceipt): AuthorityChangesetReceipt {
74
+ return this.request<AuthorityChangesetReceipt>(root, "sealChangesetReceipt", [changesetRoot, receipt]);
75
+ }
76
+
77
+ openChangesetReceipt(root: string, changesetRoot: string): AuthorityChangesetReceipt {
78
+ return this.request<AuthorityChangesetReceipt>(root, "openChangesetReceipt", [changesetRoot]);
79
+ }
80
+
81
+ private request<T = unknown>(root: string, method: string, args: unknown[] = []): T {
82
+ const baseRequest: AuthoritySessionRequest = {
83
+ schemaVersion: 1,
84
+ method,
85
+ root,
86
+ args
87
+ };
88
+ const request = this.controllerToken ? attachControllerProof(baseRequest, this.controllerToken) : baseRequest;
89
+ const stdout = execFileSync(process.execPath, [this.cliPath, "__session-request"], {
90
+ cwd: root,
91
+ input: JSON.stringify({ root, request }),
92
+ encoding: "utf8",
93
+ windowsHide: true
94
+ });
95
+ const response = JSON.parse(stdout) as AuthoritySessionResponse;
96
+ if (!response.ok) {
97
+ throw new AwbsError(response.error);
98
+ }
99
+ if (this.controllerToken && !verifyControllerResponseProof(this.controllerToken, request, response)) {
100
+ throw new AwbsError("Authority session response proof is invalid.");
101
+ }
102
+ return response.result as T;
103
+ }
104
+ }
105
+
106
+ export class AutoAuthorityAdapter implements AuthorityPort {
107
+ private readonly files: FileDatabasePort;
108
+ private readonly local: SealedAuthorityAdapter;
109
+ private readonly session: SessionAuthorityClientAdapter;
110
+
111
+ constructor(files: FileDatabasePort, cliPath: string) {
112
+ this.files = files;
113
+ this.local = new SealedAuthorityAdapter(files);
114
+ this.session = new SessionAuthorityClientAdapter(cliPath);
115
+ }
116
+
117
+ ensureInitialized(root: string): void {
118
+ this.activeAuthority(root).ensureInitialized(root);
119
+ }
120
+
121
+ createView(root: string, contract: AuthorityViewContract): AuthorityViewContract {
122
+ return this.activeAuthority(root).createView(root, contract);
123
+ }
124
+
125
+ getViewContract(root: string, viewId: string, options?: { allowRevoked?: boolean }): AuthorityViewContract {
126
+ return this.activeAuthority(root).getViewContract(root, viewId, options);
127
+ }
128
+
129
+ revokeView(root: string, viewId: string): AuthorityViewContract {
130
+ return this.activeAuthority(root).revokeView(root, viewId);
131
+ }
132
+
133
+ verify(root: string): AuthorityVerifyReport {
134
+ return this.activeAuthority(root).verify(root);
135
+ }
136
+
137
+ repairMirrors(root: string): AuthorityRepairReport {
138
+ return this.activeAuthority(root).repairMirrors(root);
139
+ }
140
+
141
+ readCatalog(root: string): AuthorityCatalog {
142
+ return this.activeAuthority(root).readCatalog(root);
143
+ }
144
+
145
+ hasLedger(root: string): boolean {
146
+ return this.activeAuthority(root).hasLedger(root);
147
+ }
148
+
149
+ bootstrapLedger(root: string, parentTrustedCommit: string): AuthorityLedger {
150
+ return this.activeAuthority(root).bootstrapLedger(root, parentTrustedCommit);
151
+ }
152
+
153
+ readLedger(root: string): AuthorityLedger {
154
+ return this.activeAuthority(root).readLedger(root);
155
+ }
156
+
157
+ recordChangesetApply(root: string, operation: AuthorityChangesetApplyOperation): AuthorityLedgerEntry {
158
+ return this.activeAuthority(root).recordChangesetApply(root, operation);
159
+ }
160
+
161
+ sealChangesetReceipt(root: string, changesetRoot: string, receipt: AuthorityChangesetReceipt): AuthorityChangesetReceipt {
162
+ return this.activeAuthority(root).sealChangesetReceipt(root, changesetRoot, receipt);
163
+ }
164
+
165
+ openChangesetReceipt(root: string, changesetRoot: string): AuthorityChangesetReceipt {
166
+ return this.activeAuthority(root).openChangesetReceipt(root, changesetRoot);
167
+ }
168
+
169
+ private activeAuthority(root: string): AuthorityPort {
170
+ const sessionPath = join(root, ".awbs", "private", "session.json");
171
+ if (this.files.pathExists(sessionPath)) {
172
+ return this.session;
173
+ }
174
+ return this.local;
175
+ }
176
+ }
@@ -0,0 +1,176 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { IndexEntry, IndexKind, IndexStatus, SummarySource } from "../domain/types.ts";
3
+ import type { FileDatabasePort } from "../ports/file-database.ts";
4
+ import type { IndexStorePort } from "../ports/index-store.ts";
5
+
6
+ type FileRow = {
7
+ json: string;
8
+ };
9
+
10
+ export class SqliteIndexStoreAdapter implements IndexStorePort {
11
+ private readonly files: FileDatabasePort;
12
+
13
+ constructor(files: FileDatabasePort) {
14
+ this.files = files;
15
+ }
16
+
17
+ readIndex(indexFile: string): IndexEntry[] {
18
+ if (this.files.pathExists(indexFile)) {
19
+ const db = new DatabaseSync(indexFile);
20
+ try {
21
+ ensureSchema(db);
22
+ return readAllEntries(db);
23
+ } finally {
24
+ db.close();
25
+ }
26
+ }
27
+ return [];
28
+ }
29
+
30
+ writeIndex(indexFile: string, entries: IndexEntry[]): void {
31
+ const db = new DatabaseSync(indexFile);
32
+ try {
33
+ ensureSchema(db);
34
+ db.exec("BEGIN");
35
+ try {
36
+ db.exec("DELETE FROM files_fts");
37
+ db.exec("DELETE FROM files");
38
+ const insertFile = db.prepare(`
39
+ INSERT INTO files (
40
+ path, kind, sha256, size, mtime, commit_hash, status, summary, summary_source, json
41
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
42
+ `);
43
+ const insertFts = db.prepare("INSERT INTO files_fts(rowid, path, summary) VALUES (?, ?, ?)");
44
+ const selectId = db.prepare("SELECT id FROM files WHERE path = ?");
45
+
46
+ for (const entry of entries) {
47
+ insertFile.run(
48
+ entry.path,
49
+ entry.kind,
50
+ entry.sha256,
51
+ entry.size,
52
+ entry.mtime,
53
+ entry.commit,
54
+ entry.status,
55
+ entry.summary,
56
+ entry.summarySource ?? "fallback",
57
+ JSON.stringify(normalizeEntry(entry))
58
+ );
59
+ const row = selectId.get(entry.path) as { id: number };
60
+ insertFts.run(row.id, entry.path, entry.summary);
61
+ }
62
+ db.exec("COMMIT");
63
+ } catch (error) {
64
+ db.exec("ROLLBACK");
65
+ throw error;
66
+ }
67
+ } finally {
68
+ db.close();
69
+ }
70
+ }
71
+
72
+ queryIndex(indexFile: string, term: string | null, options: { status?: IndexStatus | "all" }): IndexEntry[] {
73
+ if (!this.files.pathExists(indexFile)) {
74
+ return [];
75
+ }
76
+
77
+ const db = new DatabaseSync(indexFile);
78
+ try {
79
+ ensureSchema(db);
80
+ const status = options.status ?? "active";
81
+ if (!term) {
82
+ return queryFiles(db, status);
83
+ }
84
+
85
+ const ftsRows = queryFts(db, term, status);
86
+ if (ftsRows.length > 0) {
87
+ return rowsToEntries(ftsRows);
88
+ }
89
+ return rowsToEntries(queryLike(db, term, status));
90
+ } finally {
91
+ db.close();
92
+ }
93
+ }
94
+ }
95
+
96
+ function ensureSchema(db: DatabaseSync): void {
97
+ db.exec(`
98
+ PRAGMA journal_mode = WAL;
99
+ CREATE TABLE IF NOT EXISTS files (
100
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
101
+ path TEXT NOT NULL UNIQUE,
102
+ kind TEXT NOT NULL,
103
+ sha256 TEXT,
104
+ size INTEGER,
105
+ mtime TEXT NOT NULL,
106
+ commit_hash TEXT,
107
+ status TEXT NOT NULL,
108
+ summary TEXT NOT NULL,
109
+ summary_source TEXT NOT NULL,
110
+ json TEXT NOT NULL
111
+ );
112
+ CREATE INDEX IF NOT EXISTS idx_files_status ON files(status);
113
+ CREATE INDEX IF NOT EXISTS idx_files_path ON files(path);
114
+ CREATE VIRTUAL TABLE IF NOT EXISTS files_fts USING fts5(
115
+ path,
116
+ summary,
117
+ content='files',
118
+ content_rowid='id'
119
+ );
120
+ `);
121
+ }
122
+
123
+ function readAllEntries(db: DatabaseSync): IndexEntry[] {
124
+ return rowsToEntries(db.prepare("SELECT json FROM files ORDER BY path").all() as FileRow[]);
125
+ }
126
+
127
+ function queryFiles(db: DatabaseSync, status: IndexStatus | "all"): IndexEntry[] {
128
+ const statement = status === "all" ? db.prepare("SELECT json FROM files ORDER BY path") : db.prepare("SELECT json FROM files WHERE status = ? ORDER BY path");
129
+ const rows = (status === "all" ? statement.all() : statement.all(status)) as FileRow[];
130
+ return rowsToEntries(rows);
131
+ }
132
+
133
+ function queryFts(db: DatabaseSync, term: string, status: IndexStatus | "all"): FileRow[] {
134
+ try {
135
+ const match = quoteFtsTerm(term);
136
+ const where = status === "all" ? "files_fts MATCH ?" : "files_fts MATCH ? AND files.status = ?";
137
+ const statement = db.prepare(`
138
+ SELECT files.json
139
+ FROM files_fts
140
+ JOIN files ON files.id = files_fts.rowid
141
+ WHERE ${where}
142
+ ORDER BY bm25(files_fts), files.path
143
+ `);
144
+ return (status === "all" ? statement.all(match) : statement.all(match, status)) as FileRow[];
145
+ } catch {
146
+ return [];
147
+ }
148
+ }
149
+
150
+ function queryLike(db: DatabaseSync, term: string, status: IndexStatus | "all"): FileRow[] {
151
+ const needle = `%${escapeLike(term.toLowerCase())}%`;
152
+ const where = status === "all"
153
+ ? "(lower(path) LIKE ? ESCAPE '\\' OR lower(summary) LIKE ? ESCAPE '\\')"
154
+ : "status = ? AND (lower(path) LIKE ? ESCAPE '\\' OR lower(summary) LIKE ? ESCAPE '\\')";
155
+ const statement = db.prepare(`SELECT json FROM files WHERE ${where} ORDER BY path`);
156
+ return (status === "all" ? statement.all(needle, needle) : statement.all(status, needle, needle)) as FileRow[];
157
+ }
158
+
159
+ function rowsToEntries(rows: FileRow[]): IndexEntry[] {
160
+ return rows.map((row) => JSON.parse(row.json) as IndexEntry);
161
+ }
162
+
163
+ function normalizeEntry(entry: IndexEntry): IndexEntry & { kind: IndexKind; summarySource: SummarySource } {
164
+ return {
165
+ ...entry,
166
+ summarySource: entry.summarySource ?? "fallback"
167
+ };
168
+ }
169
+
170
+ function quoteFtsTerm(value: string): string {
171
+ return `"${value.replace(/"/g, "\"\"")}"`;
172
+ }
173
+
174
+ function escapeLike(value: string): string {
175
+ return value.replace(/[\\%_]/g, (char) => `\\${char}`);
176
+ }