arcane-os 0.88.0 → 0.88.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/docs/reference/cli.md +10 -0
- package/docs/reference/sdk-api.md +11 -0
- package/package.json +1 -1
- package/src/workspace-operation-lock.mjs +23 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.88.1
|
|
4
|
+
|
|
5
|
+
- Recover an ordinary workspace-operation lock when its recorded runner is
|
|
6
|
+
confirmed absent, including before the six-hour expiry. Retrying the public
|
|
7
|
+
operation uses the shared lock owner; existing published records stay readable.
|
|
8
|
+
- Preserve live or indeterminate owners and complete process-probe errors.
|
|
9
|
+
Recheck cancellation, nonce, PID and owner presence after asynchronous recovery
|
|
10
|
+
observation so an observed replacement remains owned by its actual writer.
|
|
11
|
+
The existing cooperative read/remove protocol is not an atomic cross-process
|
|
12
|
+
compare-and-delete.
|
|
13
|
+
|
|
3
14
|
## 0.88.0
|
|
4
15
|
|
|
5
16
|
- Add optional `signal` and `startLanguageModel` controls to `AI.setAI`,
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
25
25
|
share the theme, packaging, event, cancellation,
|
|
26
26
|
and browser run contracts while retaining their selected application layout.
|
|
27
27
|
|
|
28
|
-
This checkout defines the `0.88.
|
|
28
|
+
This checkout defines the `0.88.1` SDK contract. Development applications declare
|
|
29
29
|
`arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
|
|
30
30
|
designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
|
|
31
31
|
to refresh and compare the installed SDK and lockfile with the published channel;
|
package/docs/reference/cli.md
CHANGED
|
@@ -405,6 +405,16 @@ not package the app or produce `dist` output. With PWA enabled, the server
|
|
|
405
405
|
generates the installation and offline manifests directly; see
|
|
406
406
|
[PWA development and versioning](pwa.md#development-and-hosting).
|
|
407
407
|
|
|
408
|
+
Ordinary operation acquisition can recover a workspace-operation lock whose
|
|
409
|
+
recorded process is confirmed absent, including before its six-hour expiry.
|
|
410
|
+
Retry the intended command through its usual public entrypoint; no separate
|
|
411
|
+
unlock command or caller-created lease is needed. A live or indeterminate owner
|
|
412
|
+
remains `ARCANE_WORKSPACE_BUSY`, with an indeterminate process-probe error
|
|
413
|
+
retained as the cause. Recovery rechecks cancellation, nonce, PID and owner
|
|
414
|
+
presence after asynchronous observation, preserving a replacement detected
|
|
415
|
+
there. This cooperative read-then-remove protocol does not provide an
|
|
416
|
+
atomic compare-and-delete across competing processes.
|
|
417
|
+
|
|
408
418
|
The startup URL remains the selected app entry. At `/`, the server serves an
|
|
409
419
|
existing authored host-root `index.html` and suppresses the app-entry redirect
|
|
410
420
|
and PWA alias. A root outside the selected app mapping is served unchanged only
|
|
@@ -1868,6 +1868,17 @@ whole-tree replacement. The mutable result includes the installed-package
|
|
|
1868
1868
|
location, materialized workspace runtime paths, and `workspaceLock` path and
|
|
1869
1869
|
document. It does not install dependencies or merge application source.
|
|
1870
1870
|
|
|
1871
|
+
Ordinary acquisition automatically recovers a workspace-operation lock when
|
|
1872
|
+
the recorded owner process is confirmed absent, without waiting solely for its
|
|
1873
|
+
six-hour expiry. Retry this public operation normally; `workspaceOperationLease`
|
|
1874
|
+
is for active nested SDK work and cannot be reconstructed from the lock JSON.
|
|
1875
|
+
Live or indeterminate owners remain `ARCANE_WORKSPACE_BUSY`; an indeterminate
|
|
1876
|
+
process-probe error is retained as the cause. Existing published lock documents
|
|
1877
|
+
remain readable. Recovery rechecks cancellation, the captured nonce and PID,
|
|
1878
|
+
and owner presence after asynchronous observation, preserving a replacement
|
|
1879
|
+
detected there. This cooperative read-then-remove protocol does not provide an
|
|
1880
|
+
atomic compare-and-delete across competing processes.
|
|
1881
|
+
|
|
1871
1882
|
Replacement removes files absent from the selected runtime content and restores
|
|
1872
1883
|
the prior tree if commit fails. `signal` and
|
|
1873
1884
|
`onEvent` remain caller-owned before commit. Once the atomic replacement starts,
|
package/package.json
CHANGED
|
@@ -98,17 +98,20 @@ async function ensureLockDirectory(workspace){
|
|
|
98
98
|
return physicalDirectory(directory,'Workspace operation-lock directory',{create:true});
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function
|
|
101
|
+
function ownerIsAbsent(pid){
|
|
102
102
|
if(!is.safeInteger(pid)||pid<1)return false;
|
|
103
103
|
try{
|
|
104
104
|
process.kill(pid,0);
|
|
105
|
-
return
|
|
105
|
+
return false;
|
|
106
106
|
}catch(error){
|
|
107
|
-
|
|
107
|
+
if(error?.code==='ESRCH')return true;
|
|
108
|
+
const busyError=workspaceBusy('The recorded Arcane workspace operation owner could not be determined.');
|
|
109
|
+
busyError.cause=error;
|
|
110
|
+
throw busyError;
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
function lockDocument(value,{workspaceRoot
|
|
114
|
+
function lockDocument(value,{workspaceRoot}={}){
|
|
112
115
|
if(!value||!is.object(value)||is.array(value)
|
|
113
116
|
||value.schemaVersion!==1
|
|
114
117
|
||value.kind!=='arcane-workspace-operation-lock'
|
|
@@ -124,7 +127,7 @@ function lockDocument(value,{workspaceRoot,now=Date.now()}={}){
|
|
|
124
127
|
}
|
|
125
128
|
const expiresAt=Date.parse(value.expiresAt);
|
|
126
129
|
if(!is.finite(expiresAt))return null;
|
|
127
|
-
return
|
|
130
|
+
return value;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
async function readLock(lockPath,workspaceRoot){
|
|
@@ -145,16 +148,24 @@ async function readLock(lockPath,workspaceRoot){
|
|
|
145
148
|
return lockDocument(value,{workspaceRoot});
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
async function
|
|
151
|
+
async function recoverAbsentOwnerLock(lockPath,workspaceRoot,onEvent,signal){
|
|
152
|
+
throwIfAborted(signal);
|
|
149
153
|
const document=await readLock(lockPath,workspaceRoot);
|
|
150
|
-
|
|
154
|
+
throwIfAborted(signal);
|
|
155
|
+
if(!document||!ownerIsAbsent(document.owner.pid))return false;
|
|
151
156
|
await onEvent?.({
|
|
152
157
|
type:'workspace.operation.stale-recovered',
|
|
153
158
|
workspaceRoot,
|
|
154
159
|
lockPath,
|
|
155
|
-
previousOwner:document.owner,
|
|
160
|
+
previousOwner:{...document.owner},
|
|
156
161
|
previousOperation:document.operation
|
|
157
162
|
});
|
|
163
|
+
throwIfAborted(signal);
|
|
164
|
+
const current=await readLock(lockPath,workspaceRoot);
|
|
165
|
+
throwIfAborted(signal);
|
|
166
|
+
// Observers may replace the record; their completion does not retain ownership.
|
|
167
|
+
if(current?.nonce!==document.nonce||current?.owner.pid!==document.owner.pid
|
|
168
|
+
||!ownerIsAbsent(current.owner.pid))return false;
|
|
158
169
|
await rm(lockPath);
|
|
159
170
|
return true;
|
|
160
171
|
}
|
|
@@ -254,15 +265,17 @@ async function acquire({
|
|
|
254
265
|
lockPath=path.join(directory.canonical,LOCK_NAME);
|
|
255
266
|
let recovered=false;
|
|
256
267
|
for(;;){
|
|
268
|
+
throwIfAborted(signal);
|
|
257
269
|
try{
|
|
258
270
|
handle=await open(lockPath,OPEN_EXCLUSIVE,0o600);
|
|
259
271
|
break;
|
|
260
272
|
}catch(error){
|
|
261
273
|
if(error?.code!=='EEXIST')throw error;
|
|
262
|
-
if(recovered||!(await
|
|
274
|
+
if(recovered||!(await recoverAbsentOwnerLock(
|
|
263
275
|
lockPath,
|
|
264
276
|
workspace.canonical,
|
|
265
|
-
onEvent
|
|
277
|
+
onEvent,
|
|
278
|
+
signal
|
|
266
279
|
))){
|
|
267
280
|
fail(
|
|
268
281
|
`Workspace is locked by another Arcane operation: ${lockPath}. `
|