@rigour-labs/core 6.2.4 → 6.3.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 (180) hide show
  1. package/README.md +2 -0
  2. package/dist/deep/code-context.d.ts +28 -0
  3. package/dist/deep/code-context.js +93 -0
  4. package/dist/deep/code-context.test.d.ts +1 -0
  5. package/dist/deep/code-context.test.js +48 -0
  6. package/dist/deep/code-pass.d.ts +15 -0
  7. package/dist/deep/code-pass.js +44 -0
  8. package/dist/deep/code-review-prompt.d.ts +17 -0
  9. package/dist/deep/code-review-prompt.js +30 -0
  10. package/dist/deep/code-review-prompt.test.d.ts +1 -0
  11. package/dist/deep/code-review-prompt.test.js +20 -0
  12. package/dist/deep/code-verifier.d.ts +19 -0
  13. package/dist/deep/code-verifier.js +48 -0
  14. package/dist/deep/code-verifier.test.d.ts +1 -0
  15. package/dist/deep/code-verifier.test.js +46 -0
  16. package/dist/deep/fact-extractor.d.ts +7 -1
  17. package/dist/deep/fact-extractor.js +16 -8
  18. package/dist/deep/fact-extractor.test.js +34 -1
  19. package/dist/deep/facts-pass.d.ts +24 -0
  20. package/dist/deep/facts-pass.js +69 -0
  21. package/dist/deep/index.d.ts +6 -1
  22. package/dist/deep/index.js +5 -1
  23. package/dist/deep/parse-findings.d.ts +12 -0
  24. package/dist/deep/parse-findings.js +73 -0
  25. package/dist/deep/parse-findings.test.d.ts +1 -0
  26. package/dist/deep/parse-findings.test.js +25 -0
  27. package/dist/deep/prompts.d.ts +1 -1
  28. package/dist/deep/prompts.js +7 -8
  29. package/dist/deep/prompts.test.js +15 -0
  30. package/dist/discovery-signals.d.ts +27 -0
  31. package/dist/discovery-signals.js +96 -0
  32. package/dist/discovery.d.ts +1 -2
  33. package/dist/discovery.js +36 -49
  34. package/dist/discovery.test.js +84 -71
  35. package/dist/gates/context.js +7 -0
  36. package/dist/gates/deep-analysis.d.ts +33 -8
  37. package/dist/gates/deep-analysis.js +169 -306
  38. package/dist/gates/deep-analysis.test.d.ts +1 -0
  39. package/dist/gates/deep-analysis.test.js +86 -0
  40. package/dist/gates/deep-intent.d.ts +3 -0
  41. package/dist/gates/deep-intent.js +57 -0
  42. package/dist/gates/deep-intent.test.d.ts +1 -0
  43. package/dist/gates/deep-intent.test.js +51 -0
  44. package/dist/gates/deep-runner.d.ts +15 -0
  45. package/dist/gates/deep-runner.js +66 -0
  46. package/dist/gates/dependency.js +4 -0
  47. package/dist/gates/deprecated-apis-ast.d.ts +8 -0
  48. package/dist/gates/deprecated-apis-ast.js +25 -0
  49. package/dist/gates/deprecated-apis-rules-node.js +2 -8
  50. package/dist/gates/deprecated-apis.js +5 -1
  51. package/dist/gates/deprecated-apis.test.js +30 -0
  52. package/dist/gates/frontend-secret-exposure.js +5 -1
  53. package/dist/gates/frontend-secret-exposure.test.js +20 -0
  54. package/dist/gates/hallucinated-imports/framework-modules.d.ts +14 -0
  55. package/dist/gates/hallucinated-imports/framework-modules.js +75 -0
  56. package/dist/gates/hallucinated-imports/index.js +14 -6
  57. package/dist/gates/hallucinated-imports/js-resolver.d.ts +16 -12
  58. package/dist/gates/hallucinated-imports/js-resolver.js +65 -153
  59. package/dist/gates/hallucinated-imports/ts-path-target.js +16 -0
  60. package/dist/gates/hallucinated-imports/tsconfig-paths.d.ts +16 -0
  61. package/dist/gates/hallucinated-imports/tsconfig-paths.js +120 -0
  62. package/dist/gates/hallucinated-imports-sveltekit.test.d.ts +1 -0
  63. package/dist/gates/hallucinated-imports-sveltekit.test.js +108 -0
  64. package/dist/gates/logic-drift.js +7 -1
  65. package/dist/gates/runner.js +10 -40
  66. package/dist/gates/runner.test.js +37 -25
  67. package/dist/gates/scoped-gates.test.d.ts +1 -0
  68. package/dist/gates/scoped-gates.test.js +52 -0
  69. package/dist/gates/semantic-bugs.d.ts +23 -0
  70. package/dist/gates/semantic-bugs.js +48 -0
  71. package/dist/gates/semantic-bugs.test.d.ts +1 -0
  72. package/dist/gates/semantic-bugs.test.js +70 -0
  73. package/dist/gates/side-effect-analysis/index.js +11 -1
  74. package/dist/gates/side-effect-analysis.test.d.ts +1 -0
  75. package/dist/gates/side-effect-analysis.test.js +162 -0
  76. package/dist/gates/side-effect-helpers/pattern-detection.js +35 -7
  77. package/dist/gates/side-effect-helpers/resource-tracking.js +17 -10
  78. package/dist/gates/side-effect-helpers/scope-analysis.d.ts +6 -0
  79. package/dist/gates/side-effect-helpers/scope-analysis.js +20 -0
  80. package/dist/gates/style-drift.js +3 -0
  81. package/dist/index.d.ts +11 -2
  82. package/dist/index.js +10 -2
  83. package/dist/inference/http-download.d.ts +22 -0
  84. package/dist/inference/http-download.js +132 -0
  85. package/dist/inference/http-download.test.d.ts +1 -0
  86. package/dist/inference/http-download.test.js +109 -0
  87. package/dist/inference/index.d.ts +5 -1
  88. package/dist/inference/index.js +2 -1
  89. package/dist/inference/llama-engine.d.ts +27 -0
  90. package/dist/inference/llama-engine.js +152 -0
  91. package/dist/inference/llama-engine.test.d.ts +1 -0
  92. package/dist/inference/llama-engine.test.js +51 -0
  93. package/dist/inference/llama-process.d.ts +72 -0
  94. package/dist/inference/llama-process.js +126 -0
  95. package/dist/inference/llama-process.test.d.ts +1 -0
  96. package/dist/inference/llama-process.test.js +61 -0
  97. package/dist/inference/model-download.test.d.ts +1 -0
  98. package/dist/inference/model-download.test.js +73 -0
  99. package/dist/inference/model-manager.d.ts +14 -1
  100. package/dist/inference/model-manager.js +65 -80
  101. package/dist/inference/sidecar-provider.d.ts +19 -10
  102. package/dist/inference/sidecar-provider.js +125 -342
  103. package/dist/inference/types.d.ts +2 -0
  104. package/dist/semantic/ast.d.ts +29 -0
  105. package/dist/semantic/ast.js +122 -0
  106. package/dist/semantic/benchmark.d.ts +16 -0
  107. package/dist/semantic/benchmark.js +31 -0
  108. package/dist/semantic/benchmark.test.d.ts +1 -0
  109. package/dist/semantic/benchmark.test.js +18 -0
  110. package/dist/semantic/engine.d.ts +8 -0
  111. package/dist/semantic/engine.js +45 -0
  112. package/dist/semantic/intent/benchmark.d.ts +31 -0
  113. package/dist/semantic/intent/benchmark.js +74 -0
  114. package/dist/semantic/intent/intent-check.d.ts +43 -0
  115. package/dist/semantic/intent/intent-check.js +72 -0
  116. package/dist/semantic/intent/intent.test.d.ts +1 -0
  117. package/dist/semantic/intent/intent.test.js +46 -0
  118. package/dist/semantic/intent/parallel-reads.d.ts +23 -0
  119. package/dist/semantic/intent/parallel-reads.js +59 -0
  120. package/dist/semantic/learn/benchmark.d.ts +20 -0
  121. package/dist/semantic/learn/benchmark.js +71 -0
  122. package/dist/semantic/learn/compile.d.ts +4 -0
  123. package/dist/semantic/learn/compile.js +56 -0
  124. package/dist/semantic/learn/conditions.d.ts +22 -0
  125. package/dist/semantic/learn/conditions.js +93 -0
  126. package/dist/semantic/learn/extract.d.ts +30 -0
  127. package/dist/semantic/learn/extract.js +122 -0
  128. package/dist/semantic/learn/git-trees.d.ts +8 -0
  129. package/dist/semantic/learn/git-trees.js +32 -0
  130. package/dist/semantic/learn/identity.d.ts +18 -0
  131. package/dist/semantic/learn/identity.js +97 -0
  132. package/dist/semantic/learn/learn.d.ts +25 -0
  133. package/dist/semantic/learn/learn.js +109 -0
  134. package/dist/semantic/learn/learn.test.d.ts +1 -0
  135. package/dist/semantic/learn/learn.test.js +67 -0
  136. package/dist/semantic/learn/store.d.ts +4 -0
  137. package/dist/semantic/learn/store.js +39 -0
  138. package/dist/semantic/learn/types.d.ts +468 -0
  139. package/dist/semantic/learn/types.js +55 -0
  140. package/dist/semantic/origins.d.ts +42 -0
  141. package/dist/semantic/origins.js +149 -0
  142. package/dist/semantic/origins.test.d.ts +1 -0
  143. package/dist/semantic/origins.test.js +77 -0
  144. package/dist/semantic/program.d.ts +13 -0
  145. package/dist/semantic/program.js +62 -0
  146. package/dist/semantic/rules/credential-redirect.d.ts +2 -0
  147. package/dist/semantic/rules/credential-redirect.js +209 -0
  148. package/dist/semantic/rules/degraded-response-cached.d.ts +2 -0
  149. package/dist/semantic/rules/degraded-response-cached.js +271 -0
  150. package/dist/semantic/rules/in-memory-aggregation.d.ts +2 -0
  151. package/dist/semantic/rules/in-memory-aggregation.js +179 -0
  152. package/dist/semantic/summaries.d.ts +18 -0
  153. package/dist/semantic/summaries.js +33 -0
  154. package/dist/semantic/types.d.ts +32 -0
  155. package/dist/semantic/types.js +1 -0
  156. package/dist/storage/index.d.ts +1 -0
  157. package/dist/storage/index.js +1 -0
  158. package/dist/storage/team-diagnostics.d.ts +22 -0
  159. package/dist/storage/team-diagnostics.js +58 -0
  160. package/dist/storage/team-diagnostics.test.d.ts +1 -0
  161. package/dist/storage/team-diagnostics.test.js +51 -0
  162. package/dist/storage/team-schema.d.ts +6 -0
  163. package/dist/storage/team-schema.js +82 -0
  164. package/dist/storage/team-store.js +25 -72
  165. package/dist/templates/universal-config.js +4 -2
  166. package/dist/types/index.d.ts +114 -14
  167. package/dist/types/index.js +16 -2
  168. package/dist/utils/deep-status.d.ts +7 -0
  169. package/dist/utils/deep-status.js +11 -0
  170. package/dist/utils/diff.d.ts +3 -0
  171. package/dist/utils/diff.js +52 -0
  172. package/dist/utils/diff.test.d.ts +1 -0
  173. package/dist/utils/diff.test.js +38 -0
  174. package/dist/utils/scanner.js +13 -2
  175. package/dist/utils/scanner.test.js +22 -8
  176. package/dist/utils/scope.d.ts +15 -0
  177. package/dist/utils/scope.js +51 -0
  178. package/dist/utils/scope.test.d.ts +1 -0
  179. package/dist/utils/scope.test.js +38 -0
  180. package/package.json +6 -6
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { diagnoseMissingMembership, explainTeamConnectionError } from './team-diagnostics.js';
3
+ import { TEAM_SCHEMA } from './team-schema.js';
4
+ function pool(rows) {
5
+ return {
6
+ query: async () => {
7
+ if (rows instanceof Error)
8
+ throw rows;
9
+ return { rows, rowCount: rows.length };
10
+ },
11
+ };
12
+ }
13
+ describe('explainTeamConnectionError', () => {
14
+ it('explains an untrusted certificate chain with the CA fix', () => {
15
+ const message = explainTeamConnectionError(Object.assign(new Error('self-signed certificate in certificate chain'), { code: 'SELF_SIGNED_CERT_IN_CHAIN' }));
16
+ expect(message).toContain('NODE_EXTRA_CA_CERTS');
17
+ expect(message).toContain('sslmode=verify-full');
18
+ });
19
+ it('points a missing schema at init-schema', () => {
20
+ expect(explainTeamConnectionError({ code: '42P01', message: 'relation "rigour.memberships" does not exist' })).toContain('rigour team init-schema');
21
+ });
22
+ it('explains a rejected login and an unreachable host', () => {
23
+ expect(explainTeamConnectionError({ code: '28P01', message: 'password authentication failed' })).toContain('rejected the login');
24
+ expect(explainTeamConnectionError({ code: 'ECONNREFUSED', message: 'connect ECONNREFUSED' })).toContain('Cannot reach');
25
+ });
26
+ it('passes other errors through', () => {
27
+ expect(explainTeamConnectionError(new Error('something else'))).toBe('something else');
28
+ });
29
+ });
30
+ describe('diagnoseMissingMembership', () => {
31
+ it('names row-level security without a policy as the cause', async () => {
32
+ const message = await diagnoseMissingMembership(pool([{ table_name: 'memberships' }, { table_name: 'meta' }]));
33
+ expect(message).toContain('rigour.memberships and rigour.meta');
34
+ expect(message).toContain('init-schema');
35
+ });
36
+ it('reports a missing membership when nothing hides it', async () => {
37
+ expect(await diagnoseMissingMembership(pool([]))).toContain('no membership');
38
+ });
39
+ it('falls back to the membership message when the catalog cannot be read', async () => {
40
+ expect(await diagnoseMissingMembership(pool(new Error('permission denied')))).toContain('no membership');
41
+ });
42
+ });
43
+ describe('team schema', () => {
44
+ it('protects every team table with row-level security and a read policy', () => {
45
+ for (const table of ['lessons', 'memberships', 'meta']) {
46
+ expect(TEAM_SCHEMA).toContain(`ALTER TABLE rigour.${table} ENABLE ROW LEVEL SECURITY;`);
47
+ }
48
+ expect(TEAM_SCHEMA).toContain('CREATE POLICY memberships_read_own ON rigour.memberships FOR SELECT USING (db_role = current_user);');
49
+ expect(TEAM_SCHEMA).toContain('CREATE POLICY meta_read ON rigour.meta FOR SELECT USING (true);');
50
+ });
51
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * PostgreSQL team schema. Idempotent: re-running it on an existing database
3
+ * (`rigour team init-schema`) adds anything missing, including the RLS
4
+ * policies, without touching data.
5
+ */
6
+ export declare const TEAM_SCHEMA = "\nCREATE SCHEMA IF NOT EXISTS rigour;\nCREATE TABLE IF NOT EXISTS rigour.meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS rigour.memberships (\n db_role NAME NOT NULL,\n organization_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n role TEXT NOT NULL CHECK (role IN ('member', 'sme', 'owner')),\n PRIMARY KEY (db_role, team_id)\n);\nCREATE TABLE IF NOT EXISTS rigour.lessons (\n id TEXT PRIMARY KEY,\n organization_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n repository_id TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n visibility TEXT NOT NULL CHECK (visibility IN ('personal', 'team')),\n state TEXT NOT NULL CHECK (state IN ('candidate', 'validated', 'promoted', 'rejected', 'superseded')),\n kind TEXT NOT NULL,\n subject TEXT NOT NULL,\n evidence_json JSONB NOT NULL,\n confidence DOUBLE PRECISION NOT NULL,\n source TEXT NOT NULL,\n supersedes_id TEXT,\n created_at BIGINT NOT NULL,\n updated_at BIGINT NOT NULL\n);\nALTER TABLE rigour.lessons ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS lessons_read ON rigour.lessons;\nCREATE POLICY lessons_read ON rigour.lessons FOR SELECT USING (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND ((lessons.visibility = 'team' AND lessons.state = 'promoted')\n OR membership.actor_id = lessons.actor_id)\n )\n);\nDROP POLICY IF EXISTS lessons_write ON rigour.lessons;\nCREATE POLICY lessons_write ON rigour.lessons FOR ALL USING (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND membership.actor_id = lessons.actor_id\n AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))\n )\n) WITH CHECK (\n EXISTS (\n SELECT 1 FROM rigour.memberships membership\n WHERE membership.db_role = current_user\n AND membership.organization_id = lessons.organization_id\n AND membership.team_id = lessons.team_id\n AND membership.actor_id = lessons.actor_id\n AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))\n )\n);\n-- Row-level security on every team table. A host (for example Supabase's\n-- security advisor) may enable RLS on its own; without a policy each member\n-- login would then see no rows at all. Members read only their own\n-- membership rows (the lessons policies read memberships the same way), and\n-- meta holds only the schema version.\nALTER TABLE rigour.memberships ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS memberships_read_own ON rigour.memberships;\nCREATE POLICY memberships_read_own ON rigour.memberships FOR SELECT USING (db_role = current_user);\nALTER TABLE rigour.meta ENABLE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS meta_read ON rigour.meta;\nCREATE POLICY meta_read ON rigour.meta FOR SELECT USING (true);\nINSERT INTO rigour.meta (key, value) VALUES ('schema_version', '1')\nON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;\n";
@@ -0,0 +1,82 @@
1
+ /**
2
+ * PostgreSQL team schema. Idempotent: re-running it on an existing database
3
+ * (`rigour team init-schema`) adds anything missing, including the RLS
4
+ * policies, without touching data.
5
+ */
6
+ export const TEAM_SCHEMA = `
7
+ CREATE SCHEMA IF NOT EXISTS rigour;
8
+ CREATE TABLE IF NOT EXISTS rigour.meta (
9
+ key TEXT PRIMARY KEY,
10
+ value TEXT NOT NULL
11
+ );
12
+ CREATE TABLE IF NOT EXISTS rigour.memberships (
13
+ db_role NAME NOT NULL,
14
+ organization_id TEXT NOT NULL,
15
+ team_id TEXT NOT NULL,
16
+ actor_id TEXT NOT NULL,
17
+ role TEXT NOT NULL CHECK (role IN ('member', 'sme', 'owner')),
18
+ PRIMARY KEY (db_role, team_id)
19
+ );
20
+ CREATE TABLE IF NOT EXISTS rigour.lessons (
21
+ id TEXT PRIMARY KEY,
22
+ organization_id TEXT NOT NULL,
23
+ team_id TEXT NOT NULL,
24
+ repository_id TEXT NOT NULL,
25
+ actor_id TEXT NOT NULL,
26
+ visibility TEXT NOT NULL CHECK (visibility IN ('personal', 'team')),
27
+ state TEXT NOT NULL CHECK (state IN ('candidate', 'validated', 'promoted', 'rejected', 'superseded')),
28
+ kind TEXT NOT NULL,
29
+ subject TEXT NOT NULL,
30
+ evidence_json JSONB NOT NULL,
31
+ confidence DOUBLE PRECISION NOT NULL,
32
+ source TEXT NOT NULL,
33
+ supersedes_id TEXT,
34
+ created_at BIGINT NOT NULL,
35
+ updated_at BIGINT NOT NULL
36
+ );
37
+ ALTER TABLE rigour.lessons ENABLE ROW LEVEL SECURITY;
38
+ DROP POLICY IF EXISTS lessons_read ON rigour.lessons;
39
+ CREATE POLICY lessons_read ON rigour.lessons FOR SELECT USING (
40
+ EXISTS (
41
+ SELECT 1 FROM rigour.memberships membership
42
+ WHERE membership.db_role = current_user
43
+ AND membership.organization_id = lessons.organization_id
44
+ AND membership.team_id = lessons.team_id
45
+ AND ((lessons.visibility = 'team' AND lessons.state = 'promoted')
46
+ OR membership.actor_id = lessons.actor_id)
47
+ )
48
+ );
49
+ DROP POLICY IF EXISTS lessons_write ON rigour.lessons;
50
+ CREATE POLICY lessons_write ON rigour.lessons FOR ALL USING (
51
+ EXISTS (
52
+ SELECT 1 FROM rigour.memberships membership
53
+ WHERE membership.db_role = current_user
54
+ AND membership.organization_id = lessons.organization_id
55
+ AND membership.team_id = lessons.team_id
56
+ AND membership.actor_id = lessons.actor_id
57
+ AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))
58
+ )
59
+ ) WITH CHECK (
60
+ EXISTS (
61
+ SELECT 1 FROM rigour.memberships membership
62
+ WHERE membership.db_role = current_user
63
+ AND membership.organization_id = lessons.organization_id
64
+ AND membership.team_id = lessons.team_id
65
+ AND membership.actor_id = lessons.actor_id
66
+ AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))
67
+ )
68
+ );
69
+ -- Row-level security on every team table. A host (for example Supabase's
70
+ -- security advisor) may enable RLS on its own; without a policy each member
71
+ -- login would then see no rows at all. Members read only their own
72
+ -- membership rows (the lessons policies read memberships the same way), and
73
+ -- meta holds only the schema version.
74
+ ALTER TABLE rigour.memberships ENABLE ROW LEVEL SECURITY;
75
+ DROP POLICY IF EXISTS memberships_read_own ON rigour.memberships;
76
+ CREATE POLICY memberships_read_own ON rigour.memberships FOR SELECT USING (db_role = current_user);
77
+ ALTER TABLE rigour.meta ENABLE ROW LEVEL SECURITY;
78
+ DROP POLICY IF EXISTS meta_read ON rigour.meta;
79
+ CREATE POLICY meta_read ON rigour.meta FOR SELECT USING (true);
80
+ INSERT INTO rigour.meta (key, value) VALUES ('schema_version', '1')
81
+ ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
82
+ `;
@@ -3,6 +3,8 @@ import os from 'os';
3
3
  import path from 'path';
4
4
  import { openDatabase } from './db.js';
5
5
  import { decryptLocalPayload, encryptLocalPayload } from './local-encryption.js';
6
+ import { TEAM_SCHEMA } from './team-schema.js';
7
+ import { diagnoseMissingMembership, explainTeamConnectionError } from './team-diagnostics.js';
6
8
  import { TEAM_VECTOR_SCHEMA, backfillConfiguredTeamEmbeddings, getTeamSemanticHealth, searchConfiguredTeamKnowledge, upsertTeamLessonEmbedding, } from './team-vector-store.js';
7
9
  const DEFAULT_SEMANTIC = {
8
10
  provider: 'pgvector',
@@ -84,72 +86,6 @@ async function queuedChanges() {
84
86
  await db.close();
85
87
  }
86
88
  }
87
- const TEAM_SCHEMA = `
88
- CREATE SCHEMA IF NOT EXISTS rigour;
89
- CREATE TABLE IF NOT EXISTS rigour.meta (
90
- key TEXT PRIMARY KEY,
91
- value TEXT NOT NULL
92
- );
93
- CREATE TABLE IF NOT EXISTS rigour.memberships (
94
- db_role NAME NOT NULL,
95
- organization_id TEXT NOT NULL,
96
- team_id TEXT NOT NULL,
97
- actor_id TEXT NOT NULL,
98
- role TEXT NOT NULL CHECK (role IN ('member', 'sme', 'owner')),
99
- PRIMARY KEY (db_role, team_id)
100
- );
101
- CREATE TABLE IF NOT EXISTS rigour.lessons (
102
- id TEXT PRIMARY KEY,
103
- organization_id TEXT NOT NULL,
104
- team_id TEXT NOT NULL,
105
- repository_id TEXT NOT NULL,
106
- actor_id TEXT NOT NULL,
107
- visibility TEXT NOT NULL CHECK (visibility IN ('personal', 'team')),
108
- state TEXT NOT NULL CHECK (state IN ('candidate', 'validated', 'promoted', 'rejected', 'superseded')),
109
- kind TEXT NOT NULL,
110
- subject TEXT NOT NULL,
111
- evidence_json JSONB NOT NULL,
112
- confidence DOUBLE PRECISION NOT NULL,
113
- source TEXT NOT NULL,
114
- supersedes_id TEXT,
115
- created_at BIGINT NOT NULL,
116
- updated_at BIGINT NOT NULL
117
- );
118
- ALTER TABLE rigour.lessons ENABLE ROW LEVEL SECURITY;
119
- DROP POLICY IF EXISTS lessons_read ON rigour.lessons;
120
- CREATE POLICY lessons_read ON rigour.lessons FOR SELECT USING (
121
- EXISTS (
122
- SELECT 1 FROM rigour.memberships membership
123
- WHERE membership.db_role = current_user
124
- AND membership.organization_id = lessons.organization_id
125
- AND membership.team_id = lessons.team_id
126
- AND ((lessons.visibility = 'team' AND lessons.state = 'promoted')
127
- OR membership.actor_id = lessons.actor_id)
128
- )
129
- );
130
- DROP POLICY IF EXISTS lessons_write ON rigour.lessons;
131
- CREATE POLICY lessons_write ON rigour.lessons FOR ALL USING (
132
- EXISTS (
133
- SELECT 1 FROM rigour.memberships membership
134
- WHERE membership.db_role = current_user
135
- AND membership.organization_id = lessons.organization_id
136
- AND membership.team_id = lessons.team_id
137
- AND membership.actor_id = lessons.actor_id
138
- AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))
139
- )
140
- ) WITH CHECK (
141
- EXISTS (
142
- SELECT 1 FROM rigour.memberships membership
143
- WHERE membership.db_role = current_user
144
- AND membership.organization_id = lessons.organization_id
145
- AND membership.team_id = lessons.team_id
146
- AND membership.actor_id = lessons.actor_id
147
- AND (lessons.visibility = 'personal' OR membership.role IN ('sme', 'owner'))
148
- )
149
- );
150
- INSERT INTO rigour.meta (key, value) VALUES ('schema_version', '1')
151
- ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
152
- `;
153
89
  export async function initializeTeamSchema(databaseUrl, options = {}) {
154
90
  validateTeamDatabaseUrl(databaseUrl);
155
91
  const { Pool } = await loadPg();
@@ -171,7 +107,7 @@ export async function getTeamModeStatus() {
171
107
  }
172
108
  try {
173
109
  const result = await doctorTeamConnection(config);
174
- return result;
110
+ return result.connectivity === 'offline' ? { ...result, message: `Offline — changes queued. ${result.message}` } : result;
175
111
  }
176
112
  catch (error) {
177
113
  return {
@@ -190,7 +126,21 @@ export async function doctorTeamConnection(config) {
190
126
  if (!resolved?.databaseUrl) {
191
127
  return { mode: 'local', connectivity: 'local', queuedChanges: await queuedChanges(), message: 'Team mode is not configured.' };
192
128
  }
193
- validateTeamDatabaseUrl(resolved.databaseUrl);
129
+ const offline = async (message) => ({
130
+ mode: 'team',
131
+ connectivity: 'offline',
132
+ organizationId: resolved.organizationId,
133
+ teamId: resolved.teamId,
134
+ actorId: resolved.actorId,
135
+ queuedChanges: await queuedChanges(),
136
+ message,
137
+ });
138
+ try {
139
+ validateTeamDatabaseUrl(resolved.databaseUrl);
140
+ }
141
+ catch (error) {
142
+ return offline(explainTeamConnectionError(error));
143
+ }
194
144
  const { Pool } = await loadPg();
195
145
  const pool = new Pool({ connectionString: resolved.databaseUrl });
196
146
  try {
@@ -200,10 +150,10 @@ export async function doctorTeamConnection(config) {
200
150
  FROM rigour.memberships
201
151
  WHERE db_role = current_user AND team_id = $1 AND actor_id = $2 AND organization_id = $3`, [resolved.teamId, resolved.actorId, resolved.organizationId]);
202
152
  if (result.rowCount !== 1)
203
- throw new Error('Database role is not provisioned for this team.');
153
+ return offline(await diagnoseMissingMembership(pool));
204
154
  const schemaVersion = Number(result.rows[0].schema_version);
205
155
  if (schemaVersion !== 1)
206
- throw new Error(`Incompatible team schema version ${schemaVersion}; expected 1.`);
156
+ return offline(`Incompatible team schema version ${schemaVersion}; expected 1.`);
207
157
  const semantic = resolved.semantic ? await getTeamSemanticHealth(pool, resolved) : undefined;
208
158
  return {
209
159
  mode: 'team',
@@ -219,8 +169,11 @@ export async function doctorTeamConnection(config) {
219
169
  message: 'PostgreSQL team mode is healthy.',
220
170
  };
221
171
  }
172
+ catch (error) {
173
+ return offline(explainTeamConnectionError(error));
174
+ }
222
175
  finally {
223
- await pool.end();
176
+ await pool.end().catch(() => undefined);
224
177
  }
225
178
  }
226
179
  export async function syncTeamOutbox(options = {}) {
@@ -243,7 +196,7 @@ export async function syncTeamOutbox(options = {}) {
243
196
  WHERE db_role = current_user AND team_id = $1
244
197
  AND actor_id = $2 AND organization_id = $3`, [config.teamId, config.actorId, config.organizationId]);
245
198
  if (membership.rowCount !== 1)
246
- throw new Error('Database role is not provisioned for this team.');
199
+ throw new Error(await diagnoseMissingMembership(pool));
247
200
  for (const item of pending) {
248
201
  const lesson = await decryptLocalPayload(item.payload_json);
249
202
  try {
@@ -234,6 +234,9 @@ export const UNIVERSAL_CONFIG = {
234
234
  track_branches: true,
235
235
  track_returns: true,
236
236
  },
237
+ semantic_bugs: {
238
+ enabled: false,
239
+ },
237
240
  side_effect_analysis: {
238
241
  enabled: true,
239
242
  check_unbounded_timers: true,
@@ -251,9 +254,8 @@ export const UNIVERSAL_CONFIG = {
251
254
  pro: false,
252
255
  provider: 'local',
253
256
  threads: 4,
254
- max_tokens: 512,
255
257
  temperature: 0.1,
256
- timeout_ms: 60000,
258
+ intent_checks: false,
257
259
  checks: {
258
260
  solid: true,
259
261
  dry: true,