wyrd-scribe 0.1.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 +201 -0
- package/README.md +114 -0
- package/dist/config.d.ts +79 -0
- package/dist/config.js +215 -0
- package/dist/config.js.map +1 -0
- package/dist/frontmatter.d.ts +87 -0
- package/dist/frontmatter.js +302 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger.d.ts +69 -0
- package/dist/ledger.js +64 -0
- package/dist/ledger.js.map +1 -0
- package/dist/lineage.d.ts +195 -0
- package/dist/lineage.js +194 -0
- package/dist/lineage.js.map +1 -0
- package/dist/main.d.ts +27 -0
- package/dist/main.js +169 -0
- package/dist/main.js.map +1 -0
- package/dist/refusal.d.ts +36 -0
- package/dist/refusal.js +24 -0
- package/dist/refusal.js.map +1 -0
- package/dist/server.d.ts +34 -0
- package/dist/server.js +145 -0
- package/dist/server.js.map +1 -0
- package/dist/source.d.ts +76 -0
- package/dist/source.js +134 -0
- package/dist/source.js.map +1 -0
- package/dist/span.d.ts +117 -0
- package/dist/span.js +242 -0
- package/dist/span.js.map +1 -0
- package/dist/stamp.d.ts +297 -0
- package/dist/stamp.js +773 -0
- package/dist/stamp.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
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, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# wyrd-scribe
|
|
2
|
+
|
|
3
|
+
`wyrd-scribe` is a Tier-A MCP server for creating provenance-stamped Markdown pages inside one
|
|
4
|
+
folder you explicitly grant. Tier A is create-only: it can create a new page and append its lineage
|
|
5
|
+
record, but it cannot overwrite, rename, or delete a page, and it exposes no lineage-query tool.
|
|
6
|
+
|
|
7
|
+
## Installation and configuration
|
|
8
|
+
|
|
9
|
+
Install it with `npm install -g wyrd-scribe`.
|
|
10
|
+
The executable is `wyrd-scribe`. It communicates over stdio, so an MCP client configuration can
|
|
11
|
+
launch it directly:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"command": "wyrd-scribe",
|
|
16
|
+
"args": ["--grant", "/absolute/path/to/folder"]
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The granted folder may instead be supplied as `WYRD_GRANT`. An explicit `--grant` argument takes
|
|
21
|
+
precedence over `WYRD_GRANT`. `WYRD_SCRIBE_TIER` selects the tier and defaults to `A` when absent.
|
|
22
|
+
This build recognises tiers B and C but refuses to start with either because those tiers are not
|
|
23
|
+
implemented.
|
|
24
|
+
|
|
25
|
+
## `write_page`
|
|
26
|
+
|
|
27
|
+
The server exposes one tool with this exact input shape:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"path": "Notes/new-page.md",
|
|
32
|
+
"content": "Page text\n",
|
|
33
|
+
"derived_from": [
|
|
34
|
+
{
|
|
35
|
+
"source": "Sources/source.md",
|
|
36
|
+
"spans": [
|
|
37
|
+
{ "offset": 0, "length": 9 },
|
|
38
|
+
{ "quote": "source text" },
|
|
39
|
+
{ "offset": 0, "length": 9, "quote": "source te" }
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`path` and every `derived_from[].source` are relative to the granted folder. Each span is exactly
|
|
47
|
+
one of: byte `offset` plus positive byte `length`; a non-empty, uniquely occurring `quote`; or all
|
|
48
|
+
three fields, in which case the bytes and quote must agree. Unknown input keys are refused.
|
|
49
|
+
|
|
50
|
+
The granted folder must already contain a `.wyrd/` directory. The Scribe does not create that
|
|
51
|
+
directory. On the first eligible write it mints `.wyrd/scribe.json` with this shape:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"schema": "wyrd.scribe/v1",
|
|
56
|
+
"vault_id": "a server-minted UUID v4",
|
|
57
|
+
"write_frontmatter": false
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`write_frontmatter` defaults to `false`. Set it to `true` in the vault configuration to opt into a
|
|
62
|
+
`wyrd_lineage` key in created pages.
|
|
63
|
+
|
|
64
|
+
## Outcomes and recovery
|
|
65
|
+
|
|
66
|
+
A successful call creates the page first and then appends one LF-terminated JSON record to
|
|
67
|
+
`.wyrd/lineage.jsonl`. If the ledger append fails after page creation, the page is retained. The
|
|
68
|
+
result is `PAGE_WRITTEN_LEDGER_FAILED`, includes the created-page information and underlying
|
|
69
|
+
`cause`, and may report retained bytes. Inspect the retained page and ledger before repairing or
|
|
70
|
+
retrying; a retry at the same page path is create-only and will refuse `EXISTS`.
|
|
71
|
+
|
|
72
|
+
Each ledger line is a `wyrd.lineage/v1` object with `event: "page_written"`, a server-minted
|
|
73
|
+
`event_id`, `recorded_at`, the `wyrd-scribe` writer and version, the vault UUID, the created page's
|
|
74
|
+
path and SHA-256 content identity, and a `sources` array. Every source carries its grant-relative
|
|
75
|
+
path and content identity; every span records byte offset and length plus stored quote text, byte
|
|
76
|
+
counts, full-quote SHA-256, and whether the stored text was truncated.
|
|
77
|
+
|
|
78
|
+
When frontmatter projection is enabled, the created page receives one `wyrd_lineage` key containing
|
|
79
|
+
single-line JSON. The projection carries the schema, event, event ID, timestamp, writer, vault, and
|
|
80
|
+
sources. It deliberately omits the page identity and page content hash; `event_id` joins it to the
|
|
81
|
+
complete ledger record.
|
|
82
|
+
|
|
83
|
+
## Security boundary and limits
|
|
84
|
+
|
|
85
|
+
All granted-folder reads and writes go through `wyrd-fence`. The containment guarantees, hard-link
|
|
86
|
+
limits, residual filesystem races, and recovery consequences are defined in the Fence's
|
|
87
|
+
[authoritative Security boundary and limits section](https://github.com/MortalPastry/wyrd-mcp/blob/main/packages/wyrd-fence/README.md#security-boundary-and-limits).
|
|
88
|
+
The Scribe additionally refuses directly addressed `Arc/` targets and performs create-only page
|
|
89
|
+
writes. These controls are a scoped filesystem boundary, not an operating-system sandbox.
|
|
90
|
+
|
|
91
|
+
## Privacy and data flow
|
|
92
|
+
|
|
93
|
+
The server uses stdio only; it does not open a network transport. It reads the vault configuration
|
|
94
|
+
and cited source bytes inside the granted folder, and writes only the new page, the minted config
|
|
95
|
+
when needed, and the lineage ledger there. The connected MCP client receives tool metadata and the
|
|
96
|
+
structured result or refusal, including lineage and recovery information; it does not receive
|
|
97
|
+
absolute host paths or uncited source-file contents from the Scribe.
|
|
98
|
+
|
|
99
|
+
## Build and test from source
|
|
100
|
+
|
|
101
|
+
From the workspace root:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
npm install
|
|
105
|
+
npm --workspace wyrd-fence run build
|
|
106
|
+
npm --workspace wyrd-mcp run build
|
|
107
|
+
npm --workspace wyrd-scribe test
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The package's `test` lifecycle builds the Reader prerequisite before running the Scribe suite.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
Apache-2.0. See [LICENSE](LICENSE).
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE VAULT'S OWN CONFIG — `.wyrd/scribe.json`, read through the gate, never through `fs`.
|
|
3
|
+
*
|
|
4
|
+
* B2 rules frontmatter opt-in is PER-VAULT rather than per-call, so the answer to "does this vault
|
|
5
|
+
* carry visible lineage?" is a fact about the vault. That makes this file the vault's, not the
|
|
6
|
+
* server's, and the vault identity it carries is what the 2026-08-31 identity block requires on
|
|
7
|
+
* every lineage identity so a record that travels between Mages stays unambiguous.
|
|
8
|
+
*
|
|
9
|
+
* ⚠⚠ `.wyrd/` MUST ALREADY EXIST, AND THE SCRIBE WILL NOT CREATE IT. The fence's
|
|
10
|
+
* `createFileInGrant` requires an existing parent and the fence has no mkdir — deliberately, since
|
|
11
|
+
* a directory-creating primitive is a second write shape to get right. The alternative to refusing
|
|
12
|
+
* would be the Scribe mutating a stranger's vault merely by starting, which is the thing D7's
|
|
13
|
+
* whole tier design exists to keep the user in charge of. So an absent `.wyrd/` refuses and NAMES
|
|
14
|
+
* the directory to create.
|
|
15
|
+
*
|
|
16
|
+
* ⚠ NOTHING IS CACHED ACROSS CALLS. The config is one small read behind a gate that already
|
|
17
|
+
* resolves the path; a cache would make "which vault am I writing to?" a question about process
|
|
18
|
+
* history rather than about the vault, and the answer would go stale exactly when someone edits it.
|
|
19
|
+
* The SOURCE cache in `source.ts` is a different trade — it exists because sources are transcripts.
|
|
20
|
+
*/
|
|
21
|
+
import type { FenceRefusal, FsGate } from 'wyrd-fence';
|
|
22
|
+
import type { ScribeRefusal } from './refusal.js';
|
|
23
|
+
/** The directory the config lives in, grant-relative. Named in the refusal so the user can act. */
|
|
24
|
+
export declare const WYRD_DIR = ".wyrd";
|
|
25
|
+
export declare const CONFIG_PATH = ".wyrd/scribe.json";
|
|
26
|
+
export declare const LINEAGE_PATH = ".wyrd/lineage.jsonl";
|
|
27
|
+
export declare const CONFIG_SCHEMA = "wyrd.scribe/v1";
|
|
28
|
+
/** The ceiling: a config LARGER than this many bytes refuses `SCRIBE_CONFIG_TOO_LARGE`. */
|
|
29
|
+
export declare const CONFIG_CEILING = 16384;
|
|
30
|
+
/**
|
|
31
|
+
* ⚠ ONE BYTE OVER THE CEILING, NOT AT IT. A read of exactly the ceiling cannot distinguish "the
|
|
32
|
+
* file is exactly 16,384 bytes" from "the file is larger and this is the first window"; asking for
|
|
33
|
+
* one more byte makes `truncated` the answer to that question rather than an inference.
|
|
34
|
+
*
|
|
35
|
+
* ⚠⚠ AND `truncated` ALONE DID NOT ENFORCE THE CEILING, WHICH A ROUND-5 LENS PROBED LIVE ON
|
|
36
|
+
* 2026-09-08: a file of EXACTLY 16,385 bytes fits the window whole, so the gate reports
|
|
37
|
+
* `truncated: false` and the file was accepted one byte past the documented ceiling. `readSlice`
|
|
38
|
+
* therefore asks both questions — the gate's `truncated`, and `size > CONFIG_CEILING` — and `ST4`
|
|
39
|
+
* pins both edges by exact byte count (`M28` deletes the size clause).
|
|
40
|
+
*/
|
|
41
|
+
export declare const CONFIG_READ_LIMIT: number;
|
|
42
|
+
export interface ScribeConfig {
|
|
43
|
+
readonly schema: typeof CONFIG_SCHEMA;
|
|
44
|
+
readonly vault_id: string;
|
|
45
|
+
readonly write_frontmatter: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** The serialised form, with a trailing newline so the file is a well-formed text file. */
|
|
48
|
+
export declare function serialiseConfig(config: ScribeConfig): Buffer;
|
|
49
|
+
/**
|
|
50
|
+
* What a load did, alongside what it loaded.
|
|
51
|
+
*
|
|
52
|
+
* ⚠⚠ AN ENVELOPE RATHER THAN A FOURTH KEY ON `ScribeConfig`, AND THE SEPARATION IS THE POINT.
|
|
53
|
+
* `ScribeConfig` is the vault's own persisted document — exactly three keys, validated key-for-key
|
|
54
|
+
* by `validate` above, and an unknown key REFUSES. `created` is a fact about THIS INVOCATION and
|
|
55
|
+
* belongs to no vault, so putting it on the config value would make a per-call fact look like
|
|
56
|
+
* persisted state and would collide with the very screen that keeps the document honest.
|
|
57
|
+
*/
|
|
58
|
+
export interface LoadedConfig {
|
|
59
|
+
readonly config: ScribeConfig;
|
|
60
|
+
/** True when THIS call minted `.wyrd/scribe.json`; false when it read one that already existed. */
|
|
61
|
+
readonly created: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load the vault's config, minting it if `.wyrd/` exists and the file does not.
|
|
65
|
+
*
|
|
66
|
+
* ⚠ THE `.wyrd/` PROBE IS A LIST, NOT A STAT, because the gate exposes no stat and a list is the
|
|
67
|
+
* operation whose refusal distinguishes the cases this needs: `MISSING` means the directory is
|
|
68
|
+
* absent, `NOT_A_DIRECTORY` means something else owns the name. Both become
|
|
69
|
+
* `SCRIBE_NOT_INITIALISED` naming the directory — the user's action is identical either way — but
|
|
70
|
+
* every OTHER fence refusal (`ESCAPES`, `DENIED`, `ROOT_MOVED`) passes through UNCHANGED, because
|
|
71
|
+
* translating those into "not initialised" would tell the user to create a directory that already
|
|
72
|
+
* exists and hide the real reason.
|
|
73
|
+
*
|
|
74
|
+
* ⚠ THE LIST PROBE CANNOT SEE AN ALIASED `.wyrd/`, WHICH IS WHY THE CREATE MOVED ABOVE THE READ.
|
|
75
|
+
* `listDirInGrant` resolves through an in-grant junction on purpose — `.wyrd -> Arc` canonicalises
|
|
76
|
+
* to a directory inside the grant and lists happily. So does `readFileInGrant`. Only a WRITE path
|
|
77
|
+
* asks whether the parent is where it was spelled.
|
|
78
|
+
*/
|
|
79
|
+
export declare function loadConfig(gate: FsGate, newUuid: () => string): Promise<LoadedConfig | ScribeRefusal | FenceRefusal>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE VAULT'S OWN CONFIG — `.wyrd/scribe.json`, read through the gate, never through `fs`.
|
|
3
|
+
*
|
|
4
|
+
* B2 rules frontmatter opt-in is PER-VAULT rather than per-call, so the answer to "does this vault
|
|
5
|
+
* carry visible lineage?" is a fact about the vault. That makes this file the vault's, not the
|
|
6
|
+
* server's, and the vault identity it carries is what the 2026-08-31 identity block requires on
|
|
7
|
+
* every lineage identity so a record that travels between Mages stays unambiguous.
|
|
8
|
+
*
|
|
9
|
+
* ⚠⚠ `.wyrd/` MUST ALREADY EXIST, AND THE SCRIBE WILL NOT CREATE IT. The fence's
|
|
10
|
+
* `createFileInGrant` requires an existing parent and the fence has no mkdir — deliberately, since
|
|
11
|
+
* a directory-creating primitive is a second write shape to get right. The alternative to refusing
|
|
12
|
+
* would be the Scribe mutating a stranger's vault merely by starting, which is the thing D7's
|
|
13
|
+
* whole tier design exists to keep the user in charge of. So an absent `.wyrd/` refuses and NAMES
|
|
14
|
+
* the directory to create.
|
|
15
|
+
*
|
|
16
|
+
* ⚠ NOTHING IS CACHED ACROSS CALLS. The config is one small read behind a gate that already
|
|
17
|
+
* resolves the path; a cache would make "which vault am I writing to?" a question about process
|
|
18
|
+
* history rather than about the vault, and the answer would go stale exactly when someone edits it.
|
|
19
|
+
* The SOURCE cache in `source.ts` is a different trade — it exists because sources are transcripts.
|
|
20
|
+
*/
|
|
21
|
+
import { isRefusal } from 'wyrd-fence';
|
|
22
|
+
import { scribeRefuse } from './refusal.js';
|
|
23
|
+
/** The directory the config lives in, grant-relative. Named in the refusal so the user can act. */
|
|
24
|
+
export const WYRD_DIR = '.wyrd';
|
|
25
|
+
export const CONFIG_PATH = '.wyrd/scribe.json';
|
|
26
|
+
export const LINEAGE_PATH = '.wyrd/lineage.jsonl';
|
|
27
|
+
export const CONFIG_SCHEMA = 'wyrd.scribe/v1';
|
|
28
|
+
/** The ceiling: a config LARGER than this many bytes refuses `SCRIBE_CONFIG_TOO_LARGE`. */
|
|
29
|
+
export const CONFIG_CEILING = 16_384;
|
|
30
|
+
/**
|
|
31
|
+
* ⚠ ONE BYTE OVER THE CEILING, NOT AT IT. A read of exactly the ceiling cannot distinguish "the
|
|
32
|
+
* file is exactly 16,384 bytes" from "the file is larger and this is the first window"; asking for
|
|
33
|
+
* one more byte makes `truncated` the answer to that question rather than an inference.
|
|
34
|
+
*
|
|
35
|
+
* ⚠⚠ AND `truncated` ALONE DID NOT ENFORCE THE CEILING, WHICH A ROUND-5 LENS PROBED LIVE ON
|
|
36
|
+
* 2026-09-08: a file of EXACTLY 16,385 bytes fits the window whole, so the gate reports
|
|
37
|
+
* `truncated: false` and the file was accepted one byte past the documented ceiling. `readSlice`
|
|
38
|
+
* therefore asks both questions — the gate's `truncated`, and `size > CONFIG_CEILING` — and `ST4`
|
|
39
|
+
* pins both edges by exact byte count (`M28` deletes the size clause).
|
|
40
|
+
*/
|
|
41
|
+
export const CONFIG_READ_LIMIT = CONFIG_CEILING + 1;
|
|
42
|
+
/** Exactly these three keys. An unknown key REFUSES rather than being ignored — see below. */
|
|
43
|
+
const CONFIG_KEYS = new Set(['schema', 'vault_id', 'write_frontmatter']);
|
|
44
|
+
/**
|
|
45
|
+
* ⚠ UUID v4, MATCHED STRICTLY. A loose match ("any 36-char string with dashes") would admit a
|
|
46
|
+
* vault id minted by something other than this code path, and the identity block's whole point is
|
|
47
|
+
* that a lineage record travelling between Mages is unambiguous. A shape that admits anything is
|
|
48
|
+
* not an identity scheme.
|
|
49
|
+
*/
|
|
50
|
+
const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
51
|
+
/**
|
|
52
|
+
* ⚠ THE FENCE'S OWN `isRefusal` IS IMPORTED ABOVE RATHER THAN RE-DECLARED HERE — spec D8, and the
|
|
53
|
+
* same rule the package manifest states about `createFsGate`. A private copy of the guard that
|
|
54
|
+
* decides whether a fence result is a refusal is a second definition of the fence's own contract,
|
|
55
|
+
* free to drift from it silently. ⚠ `stamp.ts` deliberately keeps a private one; read the note
|
|
56
|
+
* there before assuming it was missed.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* ⚠ THE CONFIG IS PARSED WITH `Object.create(null)` SEMANTICS IN MIND — `JSON.parse` already
|
|
60
|
+
* produces a plain object whose prototype is `Object.prototype`, so `__proto__` in the JSON text
|
|
61
|
+
* lands as an ordinary own key and is caught by the unknown-key screen rather than mutating a
|
|
62
|
+
* prototype. The screen therefore does double duty and is not merely tidiness.
|
|
63
|
+
*/
|
|
64
|
+
function validate(text) {
|
|
65
|
+
let parsed;
|
|
66
|
+
try {
|
|
67
|
+
parsed = JSON.parse(text);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return scribeRefuse('SCRIBE_CONFIG_INVALID', `${CONFIG_PATH} is not valid JSON`);
|
|
71
|
+
}
|
|
72
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
73
|
+
return scribeRefuse('SCRIBE_CONFIG_INVALID', `${CONFIG_PATH} must be a JSON object`);
|
|
74
|
+
}
|
|
75
|
+
for (const key of Reflect.ownKeys(parsed)) {
|
|
76
|
+
if (typeof key !== 'string' || !CONFIG_KEYS.has(key)) {
|
|
77
|
+
return scribeRefuse('SCRIBE_CONFIG_INVALID', `${CONFIG_PATH} carries an unknown key`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const view = parsed;
|
|
81
|
+
if (view.schema !== CONFIG_SCHEMA) {
|
|
82
|
+
return scribeRefuse('SCRIBE_CONFIG_INVALID', `${CONFIG_PATH} must declare schema "${CONFIG_SCHEMA}"`);
|
|
83
|
+
}
|
|
84
|
+
if (typeof view.vault_id !== 'string' || !UUID_V4.test(view.vault_id)) {
|
|
85
|
+
return scribeRefuse('SCRIBE_CONFIG_INVALID', `${CONFIG_PATH} must carry a v4 UUID vault_id`);
|
|
86
|
+
}
|
|
87
|
+
if (typeof view.write_frontmatter !== 'boolean') {
|
|
88
|
+
return scribeRefuse('SCRIBE_CONFIG_INVALID', `${CONFIG_PATH} must carry a boolean write_frontmatter`);
|
|
89
|
+
}
|
|
90
|
+
return Object.freeze({
|
|
91
|
+
schema: CONFIG_SCHEMA,
|
|
92
|
+
vault_id: view.vault_id,
|
|
93
|
+
write_frontmatter: view.write_frontmatter
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The minted default. `write_frontmatter` is FALSE, matching D7's "defaulting to the safest":
|
|
98
|
+
* stamping a stranger's page bytes is the more visible act, so it is the one they opt into.
|
|
99
|
+
*/
|
|
100
|
+
function mint(vaultId) {
|
|
101
|
+
return Object.freeze({ schema: CONFIG_SCHEMA, vault_id: vaultId, write_frontmatter: false });
|
|
102
|
+
}
|
|
103
|
+
/** The serialised form, with a trailing newline so the file is a well-formed text file. */
|
|
104
|
+
export function serialiseConfig(config) {
|
|
105
|
+
return Buffer.from(`${JSON.stringify({ schema: config.schema, vault_id: config.vault_id, write_frontmatter: config.write_frontmatter }, null, 2)}\n`, 'utf8');
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Load the vault's config, minting it if `.wyrd/` exists and the file does not.
|
|
109
|
+
*
|
|
110
|
+
* ⚠ THE `.wyrd/` PROBE IS A LIST, NOT A STAT, because the gate exposes no stat and a list is the
|
|
111
|
+
* operation whose refusal distinguishes the cases this needs: `MISSING` means the directory is
|
|
112
|
+
* absent, `NOT_A_DIRECTORY` means something else owns the name. Both become
|
|
113
|
+
* `SCRIBE_NOT_INITIALISED` naming the directory — the user's action is identical either way — but
|
|
114
|
+
* every OTHER fence refusal (`ESCAPES`, `DENIED`, `ROOT_MOVED`) passes through UNCHANGED, because
|
|
115
|
+
* translating those into "not initialised" would tell the user to create a directory that already
|
|
116
|
+
* exists and hide the real reason.
|
|
117
|
+
*
|
|
118
|
+
* ⚠ THE LIST PROBE CANNOT SEE AN ALIASED `.wyrd/`, WHICH IS WHY THE CREATE MOVED ABOVE THE READ.
|
|
119
|
+
* `listDirInGrant` resolves through an in-grant junction on purpose — `.wyrd -> Arc` canonicalises
|
|
120
|
+
* to a directory inside the grant and lists happily. So does `readFileInGrant`. Only a WRITE path
|
|
121
|
+
* asks whether the parent is where it was spelled.
|
|
122
|
+
*/
|
|
123
|
+
export async function loadConfig(gate, newUuid) {
|
|
124
|
+
const dir = await gate.listDirInGrant(WYRD_DIR);
|
|
125
|
+
if (isRefusal(dir)) {
|
|
126
|
+
if (dir.reason === 'MISSING' || dir.reason === 'NOT_A_DIRECTORY') {
|
|
127
|
+
return scribeRefuse('SCRIBE_NOT_INITIALISED', `this vault has no ${WYRD_DIR}/ directory; create ${WYRD_DIR}/ inside the vault and retry`);
|
|
128
|
+
}
|
|
129
|
+
return dir;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* ⚠⚠ THE CREATE IS ATTEMPTED FIRST, AHEAD OF THE READ, AND THE ORDER IS THE SECURITY PROPERTY.
|
|
133
|
+
*
|
|
134
|
+
* The fence's `resolveNew` compares the resolved parent against the spelled one BEFORE it
|
|
135
|
+
* probes the leaf, so a `.wyrd -> Arc` junction is refused `PARENT_ALIAS` ahead of `EXISTS` —
|
|
136
|
+
* ahead of any open, and ahead of any page write. Read-first cannot reach that: an existing
|
|
137
|
+
* `Arc/scribe.json` answers the read successfully and the alias is never questioned, so the
|
|
138
|
+
* page is created and only the LEDGER APPEND refuses, leaving an orphan page. That orphan is
|
|
139
|
+
* the defect this ordering exists to close, and closing it needs no published API change.
|
|
140
|
+
*
|
|
141
|
+
* ⚠ `EXISTS` IS NOT AN ERROR HERE, AND THAT WAS ALREADY TRUE BEFORE THE HOIST. Two Scribe
|
|
142
|
+
* processes granted the same vault reach this line together; the fence's `wx` open makes
|
|
143
|
+
* exactly one of them the winner, and the loser must adopt the winner's id rather than refuse.
|
|
144
|
+
* Minting a second id would give one vault two identities, which is the ambiguity the identity
|
|
145
|
+
* block exists to prevent — arriving through a race instead of through a schema. What the hoist
|
|
146
|
+
* changes is that `EXISTS` is now the ORDINARY path rather than the racing one.
|
|
147
|
+
*
|
|
148
|
+
* ⚠⚠ AND THIS CREATE IS NOW THE STAMP PATH'S FIRST WRITE, WHICH THE HEADER OF `stamp.ts` USED
|
|
149
|
+
* TO DENY. Until 2026-09-08 that header said NOTHING was written until every source had
|
|
150
|
+
* resolved and every limit had passed, and named the page as the first write. Both sentences
|
|
151
|
+
* are narrowed there now, and the truth is stated here at the site that changed it: on an
|
|
152
|
+
* uninitialised vault this line creates `.wyrd/scribe.json` BEFORE a single source has been
|
|
153
|
+
* read. What the ordering still buys is the claim worth having — no PAGE, meaning no byte of
|
|
154
|
+
* caller-supplied content, exists until every check that CAN be decided without one has passed.
|
|
155
|
+
* This file is the server's own bookkeeping at a fixed path inside a directory the user made
|
|
156
|
+
* for it, and `writePage` reports it back through `config_created` rather than leaving it to be
|
|
157
|
+
* inferred.
|
|
158
|
+
*
|
|
159
|
+
* ⚠ THAT CLAUSE READ "until every check has passed" UNTIL 2026-09-08 AND IS NARROWED, because
|
|
160
|
+
* one check runs after the page exists: the final ledger-line size re-check over the canonical
|
|
161
|
+
* path (`stamp.ts` step 9), which leaves an ORPHAN page when it fires. Nothing about THIS file
|
|
162
|
+
* changes — the config mint is still the first write and still reported — but a sentence here
|
|
163
|
+
* that overstates the page guarantee is read as the guarantee, so it says what holds.
|
|
164
|
+
*
|
|
165
|
+
* ⚠⚠ THE MINTER RUNS ON EVERY LOAD, AND `loadConfig` RUNS ON EVERY STAMP THAT REACHES STEP 4
|
|
166
|
+
* (`stamp.ts`; steps 1-3 can refuse before it), SO IT RUNS AT MOST ONCE PER WRITE. Two
|
|
167
|
+
* consequences, both deliberate:
|
|
168
|
+
*
|
|
169
|
+
* · One speculative `newUuid()` per stamp is discarded when the config already exists. That
|
|
170
|
+
* is `crypto.randomUUID` in production — no I/O, no syscall worth naming.
|
|
171
|
+
* · An INJECTED `newUuid` that THROWS now throws on a stamp against a VALID EXISTING config,
|
|
172
|
+
* where before it threw only when the config was missing. It is NOT caught here, and that
|
|
173
|
+
* is a ruling rather than an omission: there is no honest reason to convert it to.
|
|
174
|
+
* `SCRIBE_CONFIG_INVALID` would blame a config that is perfectly valid, and a fence reason
|
|
175
|
+
* would attribute a Scribe-side fault to the fence. Catching it behind a fallback to the
|
|
176
|
+
* read-first order would be worse still — a containment check that switches itself off when
|
|
177
|
+
* an injected function misbehaves is not a check. The default minter does not throw; a
|
|
178
|
+
* throwing one is a broken injection, and this path reports it as one.
|
|
179
|
+
*/
|
|
180
|
+
const minted = mint(newUuid());
|
|
181
|
+
const created = await gate.createFileInGrant(CONFIG_PATH, serialiseConfig(minted));
|
|
182
|
+
// ⚠ `created: true` IS READ OFF THE ONE BRANCH THAT ACTUALLY CREATED, never inferred later from
|
|
183
|
+
// the file's presence. By the time a caller could stat it, a concurrent process minting the same
|
|
184
|
+
// vault's config makes "the file is there" true for both of them and true for neither's call.
|
|
185
|
+
if (!isRefusal(created))
|
|
186
|
+
return Object.freeze({ config: minted, created: true });
|
|
187
|
+
if (created.reason !== 'EXISTS')
|
|
188
|
+
return created;
|
|
189
|
+
const slice = await gate.readFileInGrant(CONFIG_PATH, 0, CONFIG_READ_LIMIT);
|
|
190
|
+
if (isRefusal(slice))
|
|
191
|
+
return slice;
|
|
192
|
+
// ⚠ NARROWED ON `ok`, NOT THROUGH THE FENCE'S `isRefusal`. That guard's predicate says
|
|
193
|
+
// `FenceRefusal`, and what `readSlice` can return here is a SCRIBE refusal — borrowing the
|
|
194
|
+
// fence's guard would typecheck by mislabelling the value's own union.
|
|
195
|
+
const read = readSlice(slice);
|
|
196
|
+
if ('ok' in read)
|
|
197
|
+
return read;
|
|
198
|
+
return Object.freeze({ config: read, created: false });
|
|
199
|
+
}
|
|
200
|
+
function readSlice(slice) {
|
|
201
|
+
// ⚠ TWO QUESTIONS, BECAUSE EACH ALONE WAS WRONG IN ONE DIRECTION. `truncated` is the gate's
|
|
202
|
+
// own answer to "is there more than the window holds", and it stays because the gate trims a
|
|
203
|
+
// window back to a codepoint boundary, so a byte count compared against the WINDOW can disagree
|
|
204
|
+
// with the gate about whether more remains. But a file that fits the window is not thereby
|
|
205
|
+
// under the ceiling: the window is one byte wider than the ceiling on purpose (see
|
|
206
|
+
// `CONFIG_READ_LIMIT`), so a file of exactly `CONFIG_CEILING + 1` bytes came back
|
|
207
|
+
// `truncated: false` and was accepted until 2026-09-08. `slice.size` is the file's whole
|
|
208
|
+
// length as the gate measured it, independent of any trimming, so comparing IT against the
|
|
209
|
+
// ceiling is exact.
|
|
210
|
+
if (slice.truncated || slice.size > CONFIG_CEILING) {
|
|
211
|
+
return scribeRefuse('SCRIBE_CONFIG_TOO_LARGE', `${CONFIG_PATH} is larger than ${CONFIG_CEILING} bytes`);
|
|
212
|
+
}
|
|
213
|
+
return validate(slice.bytes.toString('utf8'));
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|