@godman-protocols/soul 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,156 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship made available under
36
+ the License, as indicated by a copyright notice that is included in
37
+ or attached to the work (an example is provided in the Appendix below).
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other transformations
42
+ represent, as a whole, an original work of authorship. For the purposes
43
+ of this License, Derivative Works shall not include works that remain
44
+ separable from, or merely link (or bind by name) to the interfaces of,
45
+ the Work and Derivative Works thereof.
46
+
47
+ "Contribution" shall mean, as submitted to the Licensor for inclusion
48
+ in the Work by the copyright owner or by an individual or Legal Entity
49
+ authorized to submit on behalf of the copyright owner.
50
+
51
+ "Contributor" shall mean Licensor and any Legal Entity on behalf of
52
+ whom a Contribution has been received by the Licensor and included
53
+ within the Work.
54
+
55
+ 2. Grant of Copyright License. Subject to the terms and conditions of
56
+ this License, each Contributor hereby grants to You a perpetual,
57
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
58
+ copyright license to reproduce, prepare Derivative Works of,
59
+ publicly display, publicly perform, sublicense, and distribute the
60
+ Work and such Derivative Works in Source or Object form.
61
+
62
+ 3. Grant of Patent License. Subject to the terms and conditions of
63
+ this License, each Contributor hereby grants to You a perpetual,
64
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
65
+ (except as stated in this section) patent license to make, have made,
66
+ use, offer to sell, sell, import, and otherwise transfer the Work,
67
+ where such license applies only to those patent claims licensable
68
+ by such Contributor that are necessarily infringed by their
69
+ Contribution(s) alone or by the combination of their Contribution(s)
70
+ with the Work to which such Contribution(s) was submitted. If You
71
+ institute patent litigation against any entity (including a cross-claim
72
+ or counterclaim in a lawsuit) alleging that the Work or any other
73
+ Contribution embodied in the Work constitutes patent or contributory
74
+ patent infringement, then any patent licenses granted to You under
75
+ this License for that Work shall terminate as of the date such
76
+ litigation is filed.
77
+
78
+ 4. Redistribution. You may reproduce and distribute copies of the
79
+ Work or Derivative Works thereof in any medium, with or without
80
+ modifications, and in Source or Object form, provided that You
81
+ meet the following conditions:
82
+
83
+ (a) You must give any other recipients of the Work or Derivative
84
+ Works a copy of this License; and
85
+
86
+ (b) You must cause any modified files to carry prominent notices
87
+ stating that You changed the files; and
88
+
89
+ (c) You must retain, in the Source form of any Derivative Works
90
+ that You distribute, all copyright, patent, trademark, and
91
+ attribution notices from the Source form of the Work,
92
+ excluding those notices that do not pertain to any part of
93
+ the Derivative Works; and
94
+
95
+ (d) If the Work includes a "NOTICE" text file, You must include a
96
+ readable copy of the attribution notices contained within such
97
+ NOTICE file, as part of the Derivative Works and at least one
98
+ of the following places: within a NOTICE text file distributed
99
+ as part of the Derivative Works; within the Source form or
100
+ documentation, if provided along with the Derivative Works; or,
101
+ within a display generated by the Derivative Works, if and
102
+ wherever such third-party notices normally appear.
103
+
104
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
105
+ any Contribution intentionally submitted for inclusion in the Work
106
+ by You to the Licensor shall be under the terms and conditions of
107
+ this License, without any additional terms or conditions.
108
+
109
+ 6. Trademarks. This License does not grant permission to use the trade
110
+ names, trademarks, service marks, or product names of the Licensor,
111
+ except as required for reasonable and customary use in describing the
112
+ origin of the Work.
113
+
114
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed
115
+ to in writing, Licensor provides the Work on an "AS IS" BASIS,
116
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
117
+ implied, including, without limitation, any warranties or conditions
118
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
119
+ PARTICULAR PURPOSE. You are solely responsible for determining the
120
+ appropriateness of using or reproducing the Work and assume any
121
+ risks associated with Your exercise of permissions under this License.
122
+
123
+ 8. Limitation of Liability. In no event and under no legal theory,
124
+ whether in tort (including negligence), contract, or otherwise,
125
+ unless required by applicable law (such as deliberate and grossly
126
+ negligent acts) or agreed to in writing, shall any Contributor be
127
+ liable to You for damages, including any direct, indirect, special,
128
+ incidental, or exemplary damages of any character arising as a
129
+ result of this License or out of the use or inability to use the
130
+ Work (including but not limited to damages for loss of goodwill,
131
+ work stoppage, computer failure or malfunction, or all other
132
+ commercial damages or losses), even if such Contributor has been
133
+ advised of the possibility of such damages.
134
+
135
+ 9. Accepting Warranty or Additional Liability. While redistributing
136
+ the Work or Derivative Works thereof, You may choose to offer,
137
+ and charge a fee for, acceptance of support, warranty, indemnity,
138
+ or other liability obligations and/or rights consistent with this
139
+ License. However, in accepting such obligations, You may offer only
140
+ conditions consistent with this License.
141
+
142
+ END OF TERMS AND CONDITIONS
143
+
144
+ Copyright 2026 Godman Protocols / skingem1
145
+
146
+ Licensed under the Apache License, Version 2.0 (the "License");
147
+ you may not use this file except in compliance with the License.
148
+ You may obtain a copy of the License at
149
+
150
+ http://www.apache.org/licenses/LICENSE-2.0
151
+
152
+ Unless required by applicable law or agreed to in writing, software
153
+ distributed under the License is distributed on an "AS IS" BASIS,
154
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
155
+ See the License for the specific language governing permissions and
156
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # SOUL — Constitutional Constraints and Safety
2
+
3
+
4
+ [![npm version](https://img.shields.io/npm/v/@godman-protocols/soul.svg)](https://www.npmjs.com/package/@godman-protocols/soul)
5
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6
+ [![Node: >=20](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
7
+
8
+ > **v0.2.0** · Apache 2.0 · `@godman-protocols/soul` · Node 20+ / Deno 1.40+
9
+
10
+ SOUL is the **lowest protocol layer** of the Godman stack — a constitutional engine that encodes an operator's safety constraints and kill switches into a signed, verifiable document that every agent in the swarm evaluates before taking any action.
11
+
12
+ ```bash
13
+ npx skills add https://github.com/godman-protocols/soul
14
+ # or
15
+ npm install @godman-protocols/soul
16
+ ```
17
+
18
+ ---
19
+
20
+ ## The Problem
21
+
22
+ AI agent safety constraints are usually implemented as system prompt instructions — text that can be lost during context compaction, overridden by a jailbreak, or simply forgotten after a session restart.
23
+
24
+ This creates two critical failure modes:
25
+ - **Constraint drift** — after enough context, agents stop honouring the rules they were given
26
+ - **No auditability** — there is no verifiable record of which constraints were in force when an action was taken
27
+
28
+ SOUL solves both problems by encoding constraints as a signed, immutable constitutional document that survives context compaction (because it lives in code, not chat) and produces an append-only audit trail for every evaluation.
29
+
30
+ ---
31
+
32
+ ## Core Concepts
33
+
34
+ | Concept | What it is |
35
+ |---------|-----------|
36
+ | **Constitution** | The root document — operator identity, constraints, kill switches, HMAC signature |
37
+ | **Constraint** | A named allow/deny/require rule scoped to a resource or action pattern |
38
+ | **KillSwitch** | A non-negotiable halt trigger — fires when a runtime metric crosses a threshold |
39
+ | **EvaluationResult** | The outcome of evaluating an action against the constitution |
40
+ | **AuditEntry** | An append-only record of every evaluation (who, what, allowed/denied, why) |
41
+ | **EnforcementLevel** | `hard` (abort), `soft` (warn + log), or `advisory` (log only) |
42
+
43
+ ---
44
+
45
+ ## Quickstart
46
+
47
+ ```typescript
48
+ import {
49
+ createConstitution, signConstitution,
50
+ evaluateAction, checkKillSwitches, createAudit,
51
+ } from '@godman-protocols/soul';
52
+
53
+ const OPERATOR_SECRET = 'operator-hmac-secret';
54
+
55
+ // 1. Define the constitution
56
+ const draft = createConstitution(
57
+ 'did:kognai:tarek', // operator
58
+ [
59
+ {
60
+ name: 'Allow SCS-001 workspace access',
61
+ description: 'Agents may read and write the SCS-001 workspace',
62
+ action: 'allow',
63
+ enforcementLevel: 'hard',
64
+ scope: 'write:workspace/scs001/*',
65
+ bootstrapped: true,
66
+ },
67
+ {
68
+ name: 'Block production database writes',
69
+ description: 'No agent may write to the production database directly',
70
+ action: 'deny',
71
+ enforcementLevel: 'hard',
72
+ scope: 'write:db/prod/*',
73
+ bootstrapped: true,
74
+ },
75
+ ],
76
+ [
77
+ {
78
+ name: 'Low-engagement kill switch',
79
+ triggerCondition: 'views_per_30_posts < 500',
80
+ action: 'halt',
81
+ },
82
+ {
83
+ name: 'Memory ceiling',
84
+ triggerCondition: 'memory_gb > 22',
85
+ action: 'halt',
86
+ },
87
+ ]
88
+ );
89
+
90
+ // 2. Sign it (prevents tampering)
91
+ const constitution = signConstitution(draft, OPERATOR_SECRET);
92
+
93
+ // 3. Evaluate every action before execution
94
+ const result = evaluateAction(constitution, 'did:kognai:messi', 'write:workspace/scs001/script.json');
95
+ console.log(result.allowed); // true
96
+ console.log(result.reason); // "Allowed by constraint 'Allow SCS-001 workspace access'"
97
+
98
+ // 4. Audit every evaluation
99
+ const audit = createAudit('did:kognai:messi', 'write:workspace/scs001/script.json', result);
100
+ // → append to your audit store
101
+
102
+ // 5. Check kill switches on each monitoring cycle
103
+ const triggered = checkKillSwitches(constitution, {
104
+ views_per_30_posts: 320, // below threshold
105
+ memory_gb: 14.5,
106
+ });
107
+ if (triggered) {
108
+ console.error(`KILL SWITCH: ${triggered.name}`);
109
+ process.exit(1);
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Evaluation Order
116
+
117
+ `evaluateAction` uses a strict constitutional precedence:
118
+
119
+ 1. **Deny constraints first** — a matching `deny` rule wins regardless of any `allow` rules
120
+ 2. **Allow constraints** — the first matching `allow` rule grants access
121
+ 3. **Default deny** — if no rule matches, the action is denied (constitutional principle)
122
+
123
+ This means: to permit an action, there must be an explicit `allow` constraint. The absence of a rule is a denial.
124
+
125
+ ---
126
+
127
+ ## Kill Switches
128
+
129
+ Kill switches are **non-negotiable** — they cannot be delegated away or overridden by any other protocol:
130
+
131
+ ```typescript
132
+ const switched = checkKillSwitches(constitution, {
133
+ views_per_30_posts: 280, // < 500 → triggers halt
134
+ memory_gb: 18,
135
+ daily_oversight_hours: 7, // > 6 → triggers halt
136
+ });
137
+
138
+ if (switched) {
139
+ // switched.action === 'halt' | 'pause' | 'alert'
140
+ // Immediately stop the agent — no protocol override is valid
141
+ }
142
+ ```
143
+
144
+ **Supported operators in `triggerCondition`:** `>` · `<` · `>=` · `<=` · `==` · `!=`
145
+
146
+ **Format:** `"metric_name operator threshold"` — e.g. `"memory_gb > 22"`, `"retention_pct < 20"`
147
+
148
+ ---
149
+
150
+ ## Scope Patterns
151
+
152
+ Constraint scopes support wildcard matching:
153
+
154
+ | Pattern | Matches |
155
+ |---------|---------|
156
+ | `*` | Any action |
157
+ | `write:*` | Any write action |
158
+ | `write:workspace/*` | Any write to workspace |
159
+ | `write:workspace/scs001/*` | Any write to scs001 workspace |
160
+ | `write:workspace/scs001/script.json` | Exact match only |
161
+
162
+ ---
163
+
164
+ ## Summer Yu Rule
165
+
166
+ SOUL implements the "Summer Yu Rule" from the Kognai safety specification: **safety constraints must live in bootstrap files (SOUL constitutions, AGENTS.md, SOUL.md), never in chat context alone.** A constitution's `bootstrapped: true` flag marks constraints that must survive context compaction.
167
+
168
+ ---
169
+
170
+ ## Integration with Godman Protocols
171
+
172
+ SOUL is **always the first check** in any protocol chain:
173
+
174
+ | Layer | Check |
175
+ |-------|-------|
176
+ | **SOUL** (this) | Is the action constitutionally permitted? Are kill switches triggered? |
177
+ | **PACT** | Does the requesting agent have a valid mandate for this action? |
178
+ | **AMF** | Wrap the request in a verifiable envelope |
179
+ | **LAX** | Route to the best runtime |
180
+ | **DRS** | Allocate compute resources |
181
+ | **SIGNAL** | Emit domain events |
182
+ | **SCORE** | Evaluate outcome and update reputation |
183
+
184
+ No other protocol overrides SOUL.
185
+
186
+ ---
187
+
188
+ ## API Reference
189
+
190
+ See [`docs/api.md`](./docs/api.md) for the full API reference.
191
+
192
+ ---
193
+
194
+ ## Compatibility
195
+
196
+ | Runtime | Status |
197
+ |---------|--------|
198
+ | Node.js 20+ | ✅ Tested |
199
+ | Deno 1.40+ | ✅ Compatible |
200
+ | Bun 1.0+ | ✅ Compatible |
201
+ | Browser (ESM) | ⚠️ Requires `node:crypto` polyfill for HMAC |
202
+ | OpenClaw v2026.3+ | ✅ ClaWHub listed |
203
+ | Claude Code plugin | ✅ `.claude-plugin` included |
204
+
205
+ ---
206
+
207
+ ## Security Model
208
+
209
+ - Constitution signing uses **HMAC-SHA256** over a canonical subset of fields — it proves the document was issued by the operator and has not been tampered with.
210
+ - **Signature does not authenticate actions** — SOUL evaluates constitutions, not individual messages. Use AMF for message-level authentication.
211
+ - Kill switches are evaluated in-process. For guaranteed enforcement in adversarial settings, run SOUL evaluations in a separate trusted process or use the kill switch `action: 'halt'` to trigger a hard process exit.
212
+
213
+ ---
214
+
215
+ ## Roadmap
216
+
217
+ - [x] Core types and protocol spec
218
+ - [x] Reference implementation (TypeScript)
219
+ - [x] Constitutional evaluation (deny > allow > default-deny)
220
+ - [x] Kill switch engine (numeric threshold operators)
221
+ - [x] Audit log creation
222
+ - [x] Smoke tests (11/11 PASS)
223
+ - [ ] Python SDK
224
+ - [ ] Constitution persistence (Supabase backend)
225
+ - [ ] Multi-operator constitution merging
226
+ - [ ] Kill switch webhook delivery
227
+ - [ ] ClaWHub marketplace listing
228
+
229
+ ---
230
+
231
+ ## License
232
+
233
+ Apache License 2.0 — see [LICENSE](./LICENSE)
@@ -0,0 +1,46 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Core engine: constitution creation, evaluation, kill switches, audit
4
+ * @version 0.2.0
5
+ *
6
+ * SOUL is the lowest protocol layer. No other protocol overrides it.
7
+ * Kill switches are non-negotiable and cannot be delegated away.
8
+ */
9
+ import type { AgentId, AuditEntry, Constraint, Constitution, EvaluationResult, KillSwitch, Timestamp } from './types.js';
10
+ /**
11
+ * Create a Constitution document.
12
+ * Must be signed by the operator before use.
13
+ */
14
+ export declare function createConstitution(operatorId: string, constraints: Omit<Constraint, 'id'>[], killSwitches: Omit<KillSwitch, 'id' | 'nonNegotiable'>[], options?: {
15
+ issuedAt?: Timestamp;
16
+ }): Omit<Constitution, 'signature'> & {
17
+ signature: '';
18
+ };
19
+ /**
20
+ * Sign a constitution with the operator's secret.
21
+ */
22
+ export declare function signConstitution(constitution: Constitution, operatorSecret: string): Constitution;
23
+ /**
24
+ * Evaluate an action against the constitution.
25
+ *
26
+ * Matching order:
27
+ * 1. Check 'deny' constraints first (deny wins over allow)
28
+ * 2. Check 'require' constraints (must have a matching allow)
29
+ * 3. Check 'allow' constraints
30
+ * 4. Default: deny (constitutional principle — deny by default)
31
+ */
32
+ export declare function evaluateAction(constitution: Constitution, agentId: AgentId, action: string): EvaluationResult;
33
+ /**
34
+ * Check kill switch conditions against context.
35
+ * Context is a key-value map of runtime metrics.
36
+ *
37
+ * Trigger condition format: "key operator value"
38
+ * Supported operators: >, <, >=, <=, ==, !=
39
+ * Example: "views_per_30_posts < 500", "memory_gb > 22"
40
+ */
41
+ export declare function checkKillSwitches(constitution: Constitution, context: Record<string, number | string | boolean>): KillSwitch | null;
42
+ /**
43
+ * Create an audit entry from an evaluation result.
44
+ */
45
+ export declare function createAudit(agentId: AgentId, action: string, evaluation: EvaluationResult): AuditEntry;
46
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,UAAU,EAEV,YAAY,EAEZ,gBAAgB,EAChB,UAAU,EACV,SAAS,EACV,MAAM,YAAY,CAAC;AAMpB;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EACrC,YAAY,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,eAAe,CAAC,EAAE,EACxD,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAO,GACrC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG;IAAE,SAAS,EAAE,EAAE,CAAA;CAAE,CAarD;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,MAAM,GACrB,YAAY,CAYd;AA2BD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GACb,gBAAgB,CAqClB;AAMD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GACjD,UAAU,GAAG,IAAI,CAsBnB;AAMD;;GAEG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,gBAAgB,GAC3B,UAAU,CAQZ"}
package/dist/engine.js ADDED
@@ -0,0 +1,182 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Core engine: constitution creation, evaluation, kill switches, audit
4
+ * @version 0.2.0
5
+ *
6
+ * SOUL is the lowest protocol layer. No other protocol overrides it.
7
+ * Kill switches are non-negotiable and cannot be delegated away.
8
+ */
9
+ import { createHmac, randomUUID } from 'node:crypto';
10
+ // ---------------------------------------------------------------------------
11
+ // Constitution creation
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * Create a Constitution document.
15
+ * Must be signed by the operator before use.
16
+ */
17
+ export function createConstitution(operatorId, constraints, killSwitches, options = {}) {
18
+ return {
19
+ version: '0.1',
20
+ operatorId,
21
+ issuedAt: options.issuedAt ?? new Date().toISOString(),
22
+ constraints: constraints.map((c) => ({ ...c, id: randomUUID() })),
23
+ killSwitches: killSwitches.map((k) => ({
24
+ ...k,
25
+ id: randomUUID(),
26
+ nonNegotiable: true,
27
+ })),
28
+ signature: '',
29
+ };
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // Signing
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Sign a constitution with the operator's secret.
36
+ */
37
+ export function signConstitution(constitution, operatorSecret) {
38
+ const payload = JSON.stringify({
39
+ version: constitution.version,
40
+ operatorId: constitution.operatorId,
41
+ issuedAt: constitution.issuedAt,
42
+ constraintCount: constitution.constraints.length,
43
+ killSwitchCount: constitution.killSwitches.length,
44
+ });
45
+ const signature = createHmac('sha256', operatorSecret)
46
+ .update(payload, 'utf8')
47
+ .digest('hex');
48
+ return { ...constitution, signature };
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Scope matching
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Match an action string against a scope pattern.
55
+ * Supports:
56
+ * - Exact match: 'read:workspace/scs001'
57
+ * - Wildcard: 'read:*', '*:workspace/*', '*'
58
+ * - Prefix: 'read:workspace/*' matches 'read:workspace/scs001/file.ts'
59
+ */
60
+ function scopeMatches(pattern, action) {
61
+ if (pattern === '*')
62
+ return true;
63
+ if (pattern === action)
64
+ return true;
65
+ if (pattern.endsWith('*')) {
66
+ const prefix = pattern.slice(0, -1);
67
+ return action.startsWith(prefix);
68
+ }
69
+ return false;
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // Evaluation
73
+ // ---------------------------------------------------------------------------
74
+ /**
75
+ * Evaluate an action against the constitution.
76
+ *
77
+ * Matching order:
78
+ * 1. Check 'deny' constraints first (deny wins over allow)
79
+ * 2. Check 'require' constraints (must have a matching allow)
80
+ * 3. Check 'allow' constraints
81
+ * 4. Default: deny (constitutional principle — deny by default)
82
+ */
83
+ export function evaluateAction(constitution, agentId, action) {
84
+ const now = new Date().toISOString();
85
+ // Check deny constraints first
86
+ for (const c of constitution.constraints) {
87
+ if (c.action === 'deny' && scopeMatches(c.scope, action)) {
88
+ return {
89
+ allowed: false,
90
+ constraintId: c.id,
91
+ enforcementLevel: c.enforcementLevel,
92
+ reason: `Denied by constraint '${c.name}': ${c.description}`,
93
+ evaluatedAt: now,
94
+ };
95
+ }
96
+ }
97
+ // Check allow constraints
98
+ for (const c of constitution.constraints) {
99
+ if (c.action === 'allow' && scopeMatches(c.scope, action)) {
100
+ return {
101
+ allowed: true,
102
+ constraintId: c.id,
103
+ enforcementLevel: c.enforcementLevel,
104
+ reason: `Allowed by constraint '${c.name}'`,
105
+ evaluatedAt: now,
106
+ };
107
+ }
108
+ }
109
+ // Default deny
110
+ return {
111
+ allowed: false,
112
+ constraintId: null,
113
+ enforcementLevel: 'hard',
114
+ reason: 'No matching allow constraint — denied by default (constitutional principle)',
115
+ evaluatedAt: now,
116
+ };
117
+ }
118
+ // ---------------------------------------------------------------------------
119
+ // Kill switches
120
+ // ---------------------------------------------------------------------------
121
+ /**
122
+ * Check kill switch conditions against context.
123
+ * Context is a key-value map of runtime metrics.
124
+ *
125
+ * Trigger condition format: "key operator value"
126
+ * Supported operators: >, <, >=, <=, ==, !=
127
+ * Example: "views_per_30_posts < 500", "memory_gb > 22"
128
+ */
129
+ export function checkKillSwitches(constitution, context) {
130
+ for (const ks of constitution.killSwitches) {
131
+ const parts = ks.triggerCondition.split(/\s+/);
132
+ if (parts.length !== 3)
133
+ continue;
134
+ const [key, op, rawVal] = parts;
135
+ if (!(key in context))
136
+ continue;
137
+ const actual = Number(context[key]);
138
+ const threshold = Number(rawVal);
139
+ if (isNaN(actual) || isNaN(threshold))
140
+ continue;
141
+ let triggered = false;
142
+ switch (op) {
143
+ case '>':
144
+ triggered = actual > threshold;
145
+ break;
146
+ case '<':
147
+ triggered = actual < threshold;
148
+ break;
149
+ case '>=':
150
+ triggered = actual >= threshold;
151
+ break;
152
+ case '<=':
153
+ triggered = actual <= threshold;
154
+ break;
155
+ case '==':
156
+ triggered = actual === threshold;
157
+ break;
158
+ case '!=':
159
+ triggered = actual !== threshold;
160
+ break;
161
+ }
162
+ if (triggered)
163
+ return ks;
164
+ }
165
+ return null;
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // Audit
169
+ // ---------------------------------------------------------------------------
170
+ /**
171
+ * Create an audit entry from an evaluation result.
172
+ */
173
+ export function createAudit(agentId, action, evaluation) {
174
+ return {
175
+ id: randomUUID(),
176
+ agentId,
177
+ action,
178
+ evaluation,
179
+ timestamp: evaluation.evaluatedAt,
180
+ };
181
+ }
182
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Public API surface
4
+ * @version 0.2.0
5
+ *
6
+ * SOUL is the lowest protocol layer. No other protocol overrides it.
7
+ * Kill switches are non-negotiable and cannot be delegated away.
8
+ */
9
+ export type { AgentId, Timestamp, Signature, EnforcementLevel, ConstraintAction, Constraint, KillSwitch, Constitution, EvaluationResult, AuditEntry, } from './types.js';
10
+ export { createConstitution, signConstitution, evaluateAction, checkKillSwitches, createAudit, } from './engine.js';
11
+ /** Protocol version constant */
12
+ export declare const SOUL_VERSION: "0.2";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,gCAAgC;AAChC,eAAO,MAAM,YAAY,EAAG,KAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Public API surface
4
+ * @version 0.2.0
5
+ *
6
+ * SOUL is the lowest protocol layer. No other protocol overrides it.
7
+ * Kill switches are non-negotiable and cannot be delegated away.
8
+ */
9
+ // Engine
10
+ export { createConstitution, signConstitution, evaluateAction, checkKillSwitches, createAudit, } from './engine.js';
11
+ /** Protocol version constant */
12
+ export const SOUL_VERSION = '0.2';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgBH,SAAS;AACT,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,gCAAgC;AAChC,MAAM,CAAC,MAAM,YAAY,GAAG,KAAc,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Core type definitions (skeleton)
4
+ * @version 0.1.0-skeleton
5
+ */
6
+ export type AgentId = string;
7
+ export type Timestamp = string;
8
+ export type Signature = string;
9
+ export type EnforcementLevel = 'hard' | 'soft' | 'advisory';
10
+ export type ConstraintAction = 'allow' | 'deny' | 'require';
11
+ /** A single constitutional constraint */
12
+ export interface Constraint {
13
+ id: string;
14
+ name: string;
15
+ description: string;
16
+ action: ConstraintAction;
17
+ enforcementLevel: EnforcementLevel;
18
+ /** Resource or action pattern this constraint covers */
19
+ scope: string;
20
+ /** Whether this constraint survives context compaction (must be bootstrapped) */
21
+ bootstrapped: boolean;
22
+ }
23
+ /** A kill switch — a hard-stop trigger that halts an agent unconditionally */
24
+ export interface KillSwitch {
25
+ id: string;
26
+ name: string;
27
+ /** Human-readable trigger condition */
28
+ triggerCondition: string;
29
+ /** What to do when triggered */
30
+ action: 'halt' | 'pause' | 'alert';
31
+ /** Cannot be delegated away or overridden */
32
+ nonNegotiable: true;
33
+ }
34
+ /** The root constitutional document for an operator */
35
+ export interface Constitution {
36
+ version: '0.1';
37
+ operatorId: string;
38
+ /** ISO 8601 */
39
+ issuedAt: Timestamp;
40
+ constraints: Constraint[];
41
+ killSwitches: KillSwitch[];
42
+ /** Operator's signature over the constitution */
43
+ signature: Signature;
44
+ }
45
+ /** Result of evaluating an action against the constitution */
46
+ export interface EvaluationResult {
47
+ allowed: boolean;
48
+ constraintId: string | null;
49
+ enforcementLevel: EnforcementLevel | null;
50
+ reason: string;
51
+ evaluatedAt: Timestamp;
52
+ }
53
+ /** Append-only audit log entry */
54
+ export interface AuditEntry {
55
+ id: string;
56
+ agentId: AgentId;
57
+ action: string;
58
+ evaluation: EvaluationResult;
59
+ timestamp: Timestamp;
60
+ }
61
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAC5D,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAE5D,yCAAyC;AACzC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,gBAAgB,CAAC;IACzB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,gBAAgB,EAAE,MAAM,CAAC;IACzB,gCAAgC;IAChC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IACnC,6CAA6C;IAC7C,aAAa,EAAE,IAAI,CAAC;CACrB;AAED,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe;IACf,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,iDAAiD;IACjD,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,SAAS,CAAC;CACxB;AAED,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,gBAAgB,CAAC;IAC7B,SAAS,EAAE,SAAS,CAAC;CACtB"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Core type definitions (skeleton)
4
+ * @version 0.1.0-skeleton
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@godman-protocols/soul",
3
+ "version": "0.2.0",
4
+ "description": "Constitutional Constraints and Safety \u2014 signed constitutions, kill switches, default-deny evaluation, audit trail",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "exports": "./dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "src"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepublishOnly": "npm run build",
16
+ "test": "npx tsx smoke.test.ts",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "keywords": [
20
+ "safety",
21
+ "constitutional-ai",
22
+ "kill-switches",
23
+ "constraints",
24
+ "audit-trail",
25
+ "godman-protocols",
26
+ "multi-agent"
27
+ ],
28
+ "author": "skingem1",
29
+ "license": "Apache-2.0",
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/godman-protocols/soul.git"
37
+ },
38
+ "engines": {
39
+ "node": ">=20.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.4.0",
43
+ "tsx": "^4.7.0"
44
+ },
45
+ "dependencies": {},
46
+ "homepage": "https://github.com/skingem1/godman-protocols/tree/main/soul#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/skingem1/godman-protocols/issues"
49
+ }
50
+ }
package/src/engine.ts ADDED
@@ -0,0 +1,210 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Core engine: constitution creation, evaluation, kill switches, audit
4
+ * @version 0.2.0
5
+ *
6
+ * SOUL is the lowest protocol layer. No other protocol overrides it.
7
+ * Kill switches are non-negotiable and cannot be delegated away.
8
+ */
9
+
10
+ import { createHmac, randomUUID } from 'node:crypto';
11
+ import type {
12
+ AgentId,
13
+ AuditEntry,
14
+ Constraint,
15
+ ConstraintAction,
16
+ Constitution,
17
+ EnforcementLevel,
18
+ EvaluationResult,
19
+ KillSwitch,
20
+ Timestamp,
21
+ } from './types.js';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Constitution creation
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Create a Constitution document.
29
+ * Must be signed by the operator before use.
30
+ */
31
+ export function createConstitution(
32
+ operatorId: string,
33
+ constraints: Omit<Constraint, 'id'>[],
34
+ killSwitches: Omit<KillSwitch, 'id' | 'nonNegotiable'>[],
35
+ options: { issuedAt?: Timestamp } = {}
36
+ ): Omit<Constitution, 'signature'> & { signature: '' } {
37
+ return {
38
+ version: '0.1',
39
+ operatorId,
40
+ issuedAt: options.issuedAt ?? new Date().toISOString(),
41
+ constraints: constraints.map((c) => ({ ...c, id: randomUUID() })),
42
+ killSwitches: killSwitches.map((k) => ({
43
+ ...k,
44
+ id: randomUUID(),
45
+ nonNegotiable: true as const,
46
+ })),
47
+ signature: '',
48
+ };
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Signing
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Sign a constitution with the operator's secret.
57
+ */
58
+ export function signConstitution(
59
+ constitution: Constitution,
60
+ operatorSecret: string
61
+ ): Constitution {
62
+ const payload = JSON.stringify({
63
+ version: constitution.version,
64
+ operatorId: constitution.operatorId,
65
+ issuedAt: constitution.issuedAt,
66
+ constraintCount: constitution.constraints.length,
67
+ killSwitchCount: constitution.killSwitches.length,
68
+ });
69
+ const signature = createHmac('sha256', operatorSecret)
70
+ .update(payload, 'utf8')
71
+ .digest('hex');
72
+ return { ...constitution, signature };
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Scope matching
77
+ // ---------------------------------------------------------------------------
78
+
79
+ /**
80
+ * Match an action string against a scope pattern.
81
+ * Supports:
82
+ * - Exact match: 'read:workspace/scs001'
83
+ * - Wildcard: 'read:*', '*:workspace/*', '*'
84
+ * - Prefix: 'read:workspace/*' matches 'read:workspace/scs001/file.ts'
85
+ */
86
+ function scopeMatches(pattern: string, action: string): boolean {
87
+ if (pattern === '*') return true;
88
+ if (pattern === action) return true;
89
+ if (pattern.endsWith('*')) {
90
+ const prefix = pattern.slice(0, -1);
91
+ return action.startsWith(prefix);
92
+ }
93
+ return false;
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Evaluation
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * Evaluate an action against the constitution.
102
+ *
103
+ * Matching order:
104
+ * 1. Check 'deny' constraints first (deny wins over allow)
105
+ * 2. Check 'require' constraints (must have a matching allow)
106
+ * 3. Check 'allow' constraints
107
+ * 4. Default: deny (constitutional principle — deny by default)
108
+ */
109
+ export function evaluateAction(
110
+ constitution: Constitution,
111
+ agentId: AgentId,
112
+ action: string
113
+ ): EvaluationResult {
114
+ const now = new Date().toISOString();
115
+
116
+ // Check deny constraints first
117
+ for (const c of constitution.constraints) {
118
+ if (c.action === 'deny' && scopeMatches(c.scope, action)) {
119
+ return {
120
+ allowed: false,
121
+ constraintId: c.id,
122
+ enforcementLevel: c.enforcementLevel,
123
+ reason: `Denied by constraint '${c.name}': ${c.description}`,
124
+ evaluatedAt: now,
125
+ };
126
+ }
127
+ }
128
+
129
+ // Check allow constraints
130
+ for (const c of constitution.constraints) {
131
+ if (c.action === 'allow' && scopeMatches(c.scope, action)) {
132
+ return {
133
+ allowed: true,
134
+ constraintId: c.id,
135
+ enforcementLevel: c.enforcementLevel,
136
+ reason: `Allowed by constraint '${c.name}'`,
137
+ evaluatedAt: now,
138
+ };
139
+ }
140
+ }
141
+
142
+ // Default deny
143
+ return {
144
+ allowed: false,
145
+ constraintId: null,
146
+ enforcementLevel: 'hard',
147
+ reason: 'No matching allow constraint — denied by default (constitutional principle)',
148
+ evaluatedAt: now,
149
+ };
150
+ }
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Kill switches
154
+ // ---------------------------------------------------------------------------
155
+
156
+ /**
157
+ * Check kill switch conditions against context.
158
+ * Context is a key-value map of runtime metrics.
159
+ *
160
+ * Trigger condition format: "key operator value"
161
+ * Supported operators: >, <, >=, <=, ==, !=
162
+ * Example: "views_per_30_posts < 500", "memory_gb > 22"
163
+ */
164
+ export function checkKillSwitches(
165
+ constitution: Constitution,
166
+ context: Record<string, number | string | boolean>
167
+ ): KillSwitch | null {
168
+ for (const ks of constitution.killSwitches) {
169
+ const parts = ks.triggerCondition.split(/\s+/);
170
+ if (parts.length !== 3) continue;
171
+ const [key, op, rawVal] = parts as [string, string, string];
172
+ if (!(key in context)) continue;
173
+ const actual = Number(context[key]);
174
+ const threshold = Number(rawVal);
175
+ if (isNaN(actual) || isNaN(threshold)) continue;
176
+
177
+ let triggered = false;
178
+ switch (op) {
179
+ case '>': triggered = actual > threshold; break;
180
+ case '<': triggered = actual < threshold; break;
181
+ case '>=': triggered = actual >= threshold; break;
182
+ case '<=': triggered = actual <= threshold; break;
183
+ case '==': triggered = actual === threshold; break;
184
+ case '!=': triggered = actual !== threshold; break;
185
+ }
186
+ if (triggered) return ks;
187
+ }
188
+ return null;
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Audit
193
+ // ---------------------------------------------------------------------------
194
+
195
+ /**
196
+ * Create an audit entry from an evaluation result.
197
+ */
198
+ export function createAudit(
199
+ agentId: AgentId,
200
+ action: string,
201
+ evaluation: EvaluationResult
202
+ ): AuditEntry {
203
+ return {
204
+ id: randomUUID(),
205
+ agentId,
206
+ action,
207
+ evaluation,
208
+ timestamp: evaluation.evaluatedAt,
209
+ };
210
+ }
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Public API surface
4
+ * @version 0.2.0
5
+ *
6
+ * SOUL is the lowest protocol layer. No other protocol overrides it.
7
+ * Kill switches are non-negotiable and cannot be delegated away.
8
+ */
9
+
10
+ // Types
11
+ export type {
12
+ AgentId,
13
+ Timestamp,
14
+ Signature,
15
+ EnforcementLevel,
16
+ ConstraintAction,
17
+ Constraint,
18
+ KillSwitch,
19
+ Constitution,
20
+ EvaluationResult,
21
+ AuditEntry,
22
+ } from './types.js';
23
+
24
+ // Engine
25
+ export {
26
+ createConstitution,
27
+ signConstitution,
28
+ evaluateAction,
29
+ checkKillSwitches,
30
+ createAudit,
31
+ } from './engine.js';
32
+
33
+ /** Protocol version constant */
34
+ export const SOUL_VERSION = '0.2' as const;
package/src/types.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SOUL — Constitutional Constraints and Safety
3
+ * Core type definitions (skeleton)
4
+ * @version 0.1.0-skeleton
5
+ */
6
+
7
+ export type AgentId = string;
8
+ export type Timestamp = string;
9
+ export type Signature = string;
10
+
11
+ export type EnforcementLevel = 'hard' | 'soft' | 'advisory';
12
+ export type ConstraintAction = 'allow' | 'deny' | 'require';
13
+
14
+ /** A single constitutional constraint */
15
+ export interface Constraint {
16
+ id: string;
17
+ name: string;
18
+ description: string;
19
+ action: ConstraintAction;
20
+ enforcementLevel: EnforcementLevel;
21
+ /** Resource or action pattern this constraint covers */
22
+ scope: string;
23
+ /** Whether this constraint survives context compaction (must be bootstrapped) */
24
+ bootstrapped: boolean;
25
+ }
26
+
27
+ /** A kill switch — a hard-stop trigger that halts an agent unconditionally */
28
+ export interface KillSwitch {
29
+ id: string;
30
+ name: string;
31
+ /** Human-readable trigger condition */
32
+ triggerCondition: string;
33
+ /** What to do when triggered */
34
+ action: 'halt' | 'pause' | 'alert';
35
+ /** Cannot be delegated away or overridden */
36
+ nonNegotiable: true;
37
+ }
38
+
39
+ /** The root constitutional document for an operator */
40
+ export interface Constitution {
41
+ version: '0.1';
42
+ operatorId: string;
43
+ /** ISO 8601 */
44
+ issuedAt: Timestamp;
45
+ constraints: Constraint[];
46
+ killSwitches: KillSwitch[];
47
+ /** Operator's signature over the constitution */
48
+ signature: Signature;
49
+ }
50
+
51
+ /** Result of evaluating an action against the constitution */
52
+ export interface EvaluationResult {
53
+ allowed: boolean;
54
+ constraintId: string | null;
55
+ enforcementLevel: EnforcementLevel | null;
56
+ reason: string;
57
+ evaluatedAt: Timestamp;
58
+ }
59
+
60
+ /** Append-only audit log entry */
61
+ export interface AuditEntry {
62
+ id: string;
63
+ agentId: AgentId;
64
+ action: string;
65
+ evaluation: EvaluationResult;
66
+ timestamp: Timestamp;
67
+ }