@hachej/boring-files 0.4.36 → 0.4.37

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/README.md CHANGED
@@ -8,7 +8,7 @@ Opaque resource view IDs are provider-scoped and bound to the actual backing/inc
8
8
 
9
9
  `@hachej/boring-files/workspace` exports `createWorkspaceProvider({ identity, fs, journal })`: the one provider of a workspace's files ([One place for files](../../docs/architecture/FILES-GIT-EXEC.md#one-place-for-files)). It takes a Pi `FileSystem` (a disk environment, the virtual workspace or the SQLite backend below), the host's workspace identity and a journal (`createWorkspaceJournal(connection)` from `@hachej/boring-files/journal`, receipts, intents and the last 20 versions of each file in SQLite). Revisions are Git blob ids of the bytes. A conditional write checks every expected revision and precondition inside the workspace's queue, records its intent first and its receipt last; an intent without a completion looks up as `unknown` and is never replayed. Its owner closes the connection; the provider borrows it. Node currently marks `node:sqlite` experimental.
10
10
 
11
- The provider accepts all-or-nothing create, replace and conditional delete, plus conditional rename through `renaming.rename`. It refuses working views, per-change requests and folder mutations before any effect. Logical paths are relative to the workspace root; a path whose real location leaves the root is denied. A single file is replaced atomically by every backend; a multi-file batch only by the SQLite backend, together with its receipt when the journal shares the database. Access control is the host's, at the workspace boundary: there are no per-file permissions inside a workspace, and external revocation fencing is not implemented.
11
+ The provider accepts all-or-nothing create, replace and conditional delete, plus conditional rename through `renaming.rename`. It refuses working views and folder mutations before any effect. Logical paths are relative to the workspace root; a path whose real location leaves the root is denied. A single file is replaced atomically by every backend; a multi-file batch only by the SQLite backend, together with its receipt when the journal shares the database. Access control is the host's, at the workspace boundary: there are no per-file permissions inside a workspace, and external revocation fencing is not implemented.
12
12
 
13
13
  ### Agent writes and the change feed
14
14
 
@@ -91,7 +91,7 @@ export type ResourceChange = {
91
91
  export interface PublicationRequest {
92
92
  readonly operationId: string;
93
93
  readonly changes: NonEmpty<ResourceChange>;
94
- readonly atomicity: 'all-or-nothing' | 'per-change';
94
+ readonly atomicity: 'all-or-nothing';
95
95
  /** Extra exact-content/absence dependencies, in addition to change targets.
96
96
  * Search/listing predicate evidence needs its separately qualified provider
97
97
  * integration; this minimal contract does not pretend to implement it.
@@ -149,19 +149,7 @@ export type PublicationOutcome = {
149
149
  readonly operationId: string;
150
150
  readonly reason: string;
151
151
  };
152
- /** Result association survives multiple changes/views and lost responses.
153
- * Providers validate index coverage/uniqueness and target/receipt agreement.
154
- */
155
- export interface PublicationItem {
156
- readonly changeIndex: number;
157
- readonly target: ResourceLocator;
158
- readonly outcome: PublicationOutcome;
159
- }
160
- export type PublicationResult = PublicationOutcome | {
161
- readonly kind: 'partial';
162
- readonly operationId: string;
163
- readonly items: NonEmpty<PublicationItem>;
164
- };
152
+ export type PublicationResult = PublicationOutcome;
165
153
  export interface ProviderGuarantees {
166
154
  readonly pinnedReads: boolean;
167
155
  readonly conditionalPublication: boolean;
@@ -79,7 +79,7 @@ export function publicationSnapshot(value) {
79
79
  const input = object(value);
80
80
  if (!Array.isArray(input.changes) || !input.changes.length)
81
81
  throw new TypeError('Publication requires changes');
82
- if (input.atomicity !== 'all-or-nothing' && input.atomicity !== 'per-change')
82
+ if (input.atomicity !== 'all-or-nothing')
83
83
  throw new TypeError('Invalid atomicity');
84
84
  const [first, ...rest] = input.changes;
85
85
  const changes = [change(first), ...rest.map(change)];
@@ -172,19 +172,5 @@ function publicationOutcome(value) {
172
172
  }
173
173
  /** Parse transport/provider output before treating it as acknowledgement. */
174
174
  export function parsePublicationResult(value) {
175
- const input = object(value);
176
- if (input.kind !== 'partial')
177
- return publicationOutcome(input);
178
- if (!Array.isArray(input.items) || !input.items.length)
179
- throw new TypeError('Partial publication requires items');
180
- const items = input.items.map(value => {
181
- const item = object(value);
182
- if (typeof item.changeIndex !== 'number' || !Number.isSafeInteger(item.changeIndex) || item.changeIndex < 0)
183
- throw new TypeError('Invalid change index');
184
- return { changeIndex: item.changeIndex, target: locator(item.target), outcome: publicationOutcome(item.outcome) };
185
- });
186
- const [first, ...rest] = items;
187
- if (!first || new Set(items.map(item => item.changeIndex)).size !== items.length)
188
- throw new TypeError('Invalid partial publication coverage');
189
- return { kind: 'partial', operationId: identifier(input.operationId), items: [first, ...rest] };
175
+ return publicationOutcome(object(value));
190
176
  }
@@ -76,8 +76,6 @@ export function decodeCall(input) {
76
76
  return change.kind === 'delete' ? change : { ...change, bytes: bytes(change.bytes) };
77
77
  });
78
78
  const request = publicationSnapshot({ ...value, changes });
79
- if (request.atomicity !== 'all-or-nothing')
80
- throw new TypeError('Remote per-change publication is not qualified');
81
79
  return { kind: 'publish', value: request };
82
80
  }
83
81
  export function encode(value, limit) {
@@ -148,8 +146,6 @@ function outcomeAgreement(result, operationId, selected, changes, digest) {
148
146
  }
149
147
  export function publicationResult(value, operationId, selected, request, digest) {
150
148
  const result = parsePublicationResult(value);
151
- if (result.kind === 'partial')
152
- throw new TypeError('Remote partial receipts are not qualified');
153
149
  outcomeAgreement(result, operationId, selected, request?.changes, digest);
154
150
  return result;
155
151
  }
package/dist/remote.js CHANGED
@@ -65,8 +65,6 @@ export function createResourceClient(options) {
65
65
  const captured = publicationSnapshot(request);
66
66
  let digest;
67
67
  try {
68
- if (captured.atomicity !== 'all-or-nothing')
69
- throw new TypeError('Remote per-change publication is not qualified');
70
68
  digest = await publicationDigest(captured);
71
69
  }
72
70
  catch {
package/dist/workspace.js CHANGED
@@ -234,8 +234,6 @@ export function createWorkspaceProvider(options) {
234
234
  const moving = 'from' in request ? request : undefined;
235
235
  const publication = 'changes' in request ? request : undefined;
236
236
  const digest = moving ? await renameDigest(moving) : await publicationDigest(publication);
237
- if (publication && publication.atomicity !== 'all-or-nothing')
238
- return { kind: 'unavailable', reason: 'This provider accepts all-or-nothing publication only' };
239
237
  if (publication && publication.changes.length !== 1 && !batch)
240
238
  return { kind: 'unavailable', reason: 'This provider cannot apply a multi-file batch atomically; only a single-file change is accepted' };
241
239
  const changes = moving ? [{ kind: 'delete', target: { ...moving.from, revision: moving.expectedRevision } }] : publication.changes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-files",
3
- "version": "0.4.36",
3
+ "version": "0.4.37",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "type": "module",