patchwork-os 1.1.0-beta.4.canary.552 → 1.1.0-beta.4.canary.554
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/dist/workers/actionRef.d.ts +72 -0
- package/dist/workers/actionRef.js +133 -0
- package/dist/workers/actionRef.js.map +1 -0
- package/dist/workers/outcomeStore.d.ts +57 -5
- package/dist/workers/outcomeStore.js +87 -23
- package/dist/workers/outcomeStore.js.map +1 -1
- package/dist/workers/outcomesCli.js +62 -14
- package/dist/workers/outcomesCli.js.map +1 -1
- package/dist/workers/shadowObserver.d.ts +22 -0
- package/dist/workers/shadowObserver.js +40 -2
- package/dist/workers/shadowObserver.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stable reference to a single action a worker performed, used as the join
|
|
3
|
+
* key between a run's step and the operator's outcome disposition for it.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
* ---------------
|
|
7
|
+
* The outcome join was originally keyed on `step.output.url` — a GitHub issue
|
|
8
|
+
* URL. That works for exactly one tool shape and silently no-ops for every
|
|
9
|
+
* other, because the fold's withhold branch is guarded on the URL being
|
|
10
|
+
* present (`shadowObserver.ts`). A write tool that returns no URL therefore
|
|
11
|
+
* skipped the human-confirmation check entirely and folded as earned trust.
|
|
12
|
+
* `todoist.create_task` is the concrete case: it returns 26 fields, none of
|
|
13
|
+
* them a URL, and the Todoist API exposes no permalink field at all.
|
|
14
|
+
*
|
|
15
|
+
* The rejected alternative was to synthesise a URL inside the connector. That
|
|
16
|
+
* hardcodes a web-app URL scheme we do not control, breaks silently when it
|
|
17
|
+
* changes, and fixes one connector. Instead we key on what every write tool
|
|
18
|
+
* already has and cannot lose: its own name, plus the id of the thing it just
|
|
19
|
+
* created.
|
|
20
|
+
*
|
|
21
|
+
* TWO KEY SHAPES, ON PURPOSE, WITH NO MIGRATION
|
|
22
|
+
* ---------------------------------------------
|
|
23
|
+
* Existing `outcome-log.jsonl` rows are keyed by `issueUrl` and every one of
|
|
24
|
+
* them is a real GitHub URL. Those rows are NOT rewritten. A URL is already a
|
|
25
|
+
* perfectly good stable identifier for the thing it names, the lookup is a map
|
|
26
|
+
* get either way, and rewriting the sole evidence file the autonomy gate rests
|
|
27
|
+
* on — in place, with no rollback — to gain nothing but uniformity is a bad
|
|
28
|
+
* trade. The asymmetry decides it: a missed lookup costs a WITHHOLD (the safe
|
|
29
|
+
* default), a botched migration costs the ledger.
|
|
30
|
+
*
|
|
31
|
+
* The two shapes cannot collide, and that is asserted rather than assumed:
|
|
32
|
+
* `canonicalActionRef` REFUSES to build a key that looks like a URL, so a
|
|
33
|
+
* `tool:id` key can never shadow or be shadowed by a legacy `issueUrl` key.
|
|
34
|
+
*/
|
|
35
|
+
/** A tool-scoped reference to one performed action. */
|
|
36
|
+
export interface ActionRef {
|
|
37
|
+
/** The tool that performed the action, e.g. `"todoist.create_task"`. */
|
|
38
|
+
tool: string;
|
|
39
|
+
/** The external id the tool returned for the thing it created. */
|
|
40
|
+
id: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Thrown when a caller tries to build a key that would be ambiguous against
|
|
44
|
+
* the legacy URL-shaped keys. Loud on purpose — see the header note. A deny
|
|
45
|
+
* of this kind fails the write rather than coercing it, because a coerced key
|
|
46
|
+
* would silently land in the ledger under a name nothing looks up.
|
|
47
|
+
*/
|
|
48
|
+
export declare class AmbiguousActionRefError extends Error {
|
|
49
|
+
constructor(message: string);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The canonical string key for an `ActionRef`: `"<tool>:<id>"`.
|
|
53
|
+
*
|
|
54
|
+
* Throws `AmbiguousActionRefError` when either half is empty/blank, or when
|
|
55
|
+
* the resulting key would be URL-shaped (which would put it in the same
|
|
56
|
+
* namespace as the legacy `issueUrl` keys). Both are programmer errors at a
|
|
57
|
+
* write site, not user input to be tolerated.
|
|
58
|
+
*/
|
|
59
|
+
export declare function canonicalActionRef(ref: ActionRef): string;
|
|
60
|
+
/**
|
|
61
|
+
* Derive the outcome-join key for a completed step, or null when the step's
|
|
62
|
+
* output carries nothing usable as an identifier.
|
|
63
|
+
*
|
|
64
|
+
* Returning null is meaningful and must stay distinguishable from returning a
|
|
65
|
+
* key that finds no disposition: null means "this action cannot be referred
|
|
66
|
+
* to", whereas a key with no disposition means "nobody has ruled on it yet".
|
|
67
|
+
* The two justify different fold outcomes.
|
|
68
|
+
*
|
|
69
|
+
* NOTE: a `url`/`html_url` field yields the URL itself as the key — NOT a
|
|
70
|
+
* `tool:url` composite — precisely so legacy rows keep joining.
|
|
71
|
+
*/
|
|
72
|
+
export declare function deriveActionKey(tool: string | undefined, output: unknown): string | null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stable reference to a single action a worker performed, used as the join
|
|
3
|
+
* key between a run's step and the operator's outcome disposition for it.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
* ---------------
|
|
7
|
+
* The outcome join was originally keyed on `step.output.url` — a GitHub issue
|
|
8
|
+
* URL. That works for exactly one tool shape and silently no-ops for every
|
|
9
|
+
* other, because the fold's withhold branch is guarded on the URL being
|
|
10
|
+
* present (`shadowObserver.ts`). A write tool that returns no URL therefore
|
|
11
|
+
* skipped the human-confirmation check entirely and folded as earned trust.
|
|
12
|
+
* `todoist.create_task` is the concrete case: it returns 26 fields, none of
|
|
13
|
+
* them a URL, and the Todoist API exposes no permalink field at all.
|
|
14
|
+
*
|
|
15
|
+
* The rejected alternative was to synthesise a URL inside the connector. That
|
|
16
|
+
* hardcodes a web-app URL scheme we do not control, breaks silently when it
|
|
17
|
+
* changes, and fixes one connector. Instead we key on what every write tool
|
|
18
|
+
* already has and cannot lose: its own name, plus the id of the thing it just
|
|
19
|
+
* created.
|
|
20
|
+
*
|
|
21
|
+
* TWO KEY SHAPES, ON PURPOSE, WITH NO MIGRATION
|
|
22
|
+
* ---------------------------------------------
|
|
23
|
+
* Existing `outcome-log.jsonl` rows are keyed by `issueUrl` and every one of
|
|
24
|
+
* them is a real GitHub URL. Those rows are NOT rewritten. A URL is already a
|
|
25
|
+
* perfectly good stable identifier for the thing it names, the lookup is a map
|
|
26
|
+
* get either way, and rewriting the sole evidence file the autonomy gate rests
|
|
27
|
+
* on — in place, with no rollback — to gain nothing but uniformity is a bad
|
|
28
|
+
* trade. The asymmetry decides it: a missed lookup costs a WITHHOLD (the safe
|
|
29
|
+
* default), a botched migration costs the ledger.
|
|
30
|
+
*
|
|
31
|
+
* The two shapes cannot collide, and that is asserted rather than assumed:
|
|
32
|
+
* `canonicalActionRef` REFUSES to build a key that looks like a URL, so a
|
|
33
|
+
* `tool:id` key can never shadow or be shadowed by a legacy `issueUrl` key.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Thrown when a caller tries to build a key that would be ambiguous against
|
|
37
|
+
* the legacy URL-shaped keys. Loud on purpose — see the header note. A deny
|
|
38
|
+
* of this kind fails the write rather than coercing it, because a coerced key
|
|
39
|
+
* would silently land in the ledger under a name nothing looks up.
|
|
40
|
+
*/
|
|
41
|
+
export class AmbiguousActionRefError extends Error {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "AmbiguousActionRefError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Keys of this shape belong to the legacy URL-keyed rows. */
|
|
48
|
+
const URL_LIKE = /^https?:\/\//i;
|
|
49
|
+
/**
|
|
50
|
+
* The canonical string key for an `ActionRef`: `"<tool>:<id>"`.
|
|
51
|
+
*
|
|
52
|
+
* Throws `AmbiguousActionRefError` when either half is empty/blank, or when
|
|
53
|
+
* the resulting key would be URL-shaped (which would put it in the same
|
|
54
|
+
* namespace as the legacy `issueUrl` keys). Both are programmer errors at a
|
|
55
|
+
* write site, not user input to be tolerated.
|
|
56
|
+
*/
|
|
57
|
+
export function canonicalActionRef(ref) {
|
|
58
|
+
const tool = ref.tool?.trim();
|
|
59
|
+
const id = ref.id?.trim();
|
|
60
|
+
if (!tool || !id) {
|
|
61
|
+
throw new AmbiguousActionRefError(`An action ref needs both a tool and an id (got tool=${JSON.stringify(ref.tool)}, id=${JSON.stringify(ref.id)}).`);
|
|
62
|
+
}
|
|
63
|
+
const key = `${tool}:${id}`;
|
|
64
|
+
if (URL_LIKE.test(key)) {
|
|
65
|
+
throw new AmbiguousActionRefError(`Action ref "${key}" is URL-shaped, which collides with the legacy issueUrl key namespace. A tool name must not begin with http(s).`);
|
|
66
|
+
}
|
|
67
|
+
return key;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The id fields write tools actually return, in preference order.
|
|
71
|
+
*
|
|
72
|
+
* Ordered most-specific first so a payload carrying several does not depend on
|
|
73
|
+
* object key order. `url` is LAST and deliberately included: for a tool that
|
|
74
|
+
* does return a URL (github.create_issue), the URL remains the key, which is
|
|
75
|
+
* what keeps the 95 existing `issueUrl` rows joining without a migration.
|
|
76
|
+
*/
|
|
77
|
+
const ID_FIELDS = [
|
|
78
|
+
"url",
|
|
79
|
+
"html_url",
|
|
80
|
+
"id",
|
|
81
|
+
"issueNumber",
|
|
82
|
+
"number",
|
|
83
|
+
"key",
|
|
84
|
+
"gid",
|
|
85
|
+
"ts",
|
|
86
|
+
];
|
|
87
|
+
/**
|
|
88
|
+
* Derive the outcome-join key for a completed step, or null when the step's
|
|
89
|
+
* output carries nothing usable as an identifier.
|
|
90
|
+
*
|
|
91
|
+
* Returning null is meaningful and must stay distinguishable from returning a
|
|
92
|
+
* key that finds no disposition: null means "this action cannot be referred
|
|
93
|
+
* to", whereas a key with no disposition means "nobody has ruled on it yet".
|
|
94
|
+
* The two justify different fold outcomes.
|
|
95
|
+
*
|
|
96
|
+
* NOTE: a `url`/`html_url` field yields the URL itself as the key — NOT a
|
|
97
|
+
* `tool:url` composite — precisely so legacy rows keep joining.
|
|
98
|
+
*/
|
|
99
|
+
export function deriveActionKey(tool, output) {
|
|
100
|
+
if (!tool)
|
|
101
|
+
return null;
|
|
102
|
+
if (output === null || typeof output !== "object" || Array.isArray(output)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const rec = output;
|
|
106
|
+
for (const field of ID_FIELDS) {
|
|
107
|
+
if (!Object.hasOwn(rec, field))
|
|
108
|
+
continue;
|
|
109
|
+
const raw = rec[field];
|
|
110
|
+
// Numeric ids are as legitimate as string ones (GitHub issue numbers,
|
|
111
|
+
// Slack/Asana numeric ids). Booleans and objects are not identifiers.
|
|
112
|
+
const value = typeof raw === "string"
|
|
113
|
+
? raw.trim()
|
|
114
|
+
: typeof raw === "number" && Number.isFinite(raw)
|
|
115
|
+
? String(raw)
|
|
116
|
+
: "";
|
|
117
|
+
if (!value)
|
|
118
|
+
continue;
|
|
119
|
+
// A URL field IS the key (legacy compatibility, see the note above).
|
|
120
|
+
if (URL_LIKE.test(value))
|
|
121
|
+
return value;
|
|
122
|
+
try {
|
|
123
|
+
return canonicalActionRef({ tool, id: value });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// A tool name that would make a URL-shaped key: skip this field rather
|
|
127
|
+
// than throwing on a read path. Read paths must never crash the fold.
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=actionRef.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionRef.js","sourceRoot":"","sources":["../../src/workers/actionRef.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAUH;;;;;GAKG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,QAAQ,GAAG,eAAe,CAAC;AAEjC;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAc;IAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IAC9B,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,uBAAuB,CAC/B,uDAAuD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAClH,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;IAC5B,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,uBAAuB,CAC/B,eAAe,GAAG,kHAAkH,CACrI,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,SAAS,GAAG;IAChB,KAAK;IACL,UAAU;IACV,IAAI;IACJ,aAAa;IACb,QAAQ;IACR,KAAK;IACL,KAAK;IACL,IAAI;CACI,CAAC;AAEX;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAwB,EACxB,MAAe;IAEf,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC;YAAE,SAAS;QACzC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACvB,sEAAsE;QACtE,sEAAsE;QACtE,MAAM,KAAK,GACT,OAAO,GAAG,KAAK,QAAQ;YACrB,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;YACZ,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAC/C,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;gBACb,CAAC,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,qEAAqE;QACrE,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACvC,IAAI,CAAC;YACH,OAAO,kBAAkB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,sEAAsE;YACtE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ActionRef } from "./actionRef.js";
|
|
1
2
|
/**
|
|
2
3
|
* Outcome disposition for a filed issue. Drives the trust signal the ramp
|
|
3
4
|
* folds in once the durability window has elapsed:
|
|
@@ -28,8 +29,24 @@ export type OutcomeDisposition = "confirmed" | "junk" | "unknown";
|
|
|
28
29
|
*/
|
|
29
30
|
export declare function resolveOutcomeLogDir(override?: string): string;
|
|
30
31
|
export interface OutcomeRecord {
|
|
31
|
-
/**
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* GitHub issue URL — the legacy lookup key, and still the key for any row
|
|
34
|
+
* that has one. Optional ONLY because `ref` is the alternative; exactly one
|
|
35
|
+
* of `issueUrl` / `ref` must be present or the row cannot be keyed at all
|
|
36
|
+
* (see `parseOutcomeLog`, which now REPORTS such rows rather than dropping
|
|
37
|
+
* them silently).
|
|
38
|
+
*/
|
|
39
|
+
issueUrl?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Tool-scoped reference for an action that has no URL — the generalisation
|
|
42
|
+
* that lets a worker earn trust from actions that are not GitHub issues.
|
|
43
|
+
* Keyed via `canonicalActionRef` to `"<tool>:<id>"`, a namespace that cannot
|
|
44
|
+
* collide with `issueUrl` (asserted, not assumed — see actionRef.ts).
|
|
45
|
+
*
|
|
46
|
+
* Existing rows are NOT migrated: a URL is already a fine key, and rewriting
|
|
47
|
+
* the file the autonomy gate rests on to gain uniformity is a bad trade.
|
|
48
|
+
*/
|
|
49
|
+
ref?: ActionRef;
|
|
33
50
|
disposition: OutcomeDisposition;
|
|
34
51
|
/** Epoch ms when this record was written by the ingester. */
|
|
35
52
|
checkedAt: number;
|
|
@@ -54,6 +71,23 @@ export interface OutcomeRecord {
|
|
|
54
71
|
*/
|
|
55
72
|
origin?: "manual" | "ingester";
|
|
56
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* A row the parser could not turn into a lookup key.
|
|
76
|
+
*
|
|
77
|
+
* These used to be `continue`d silently. That is the quiet-corruption vector
|
|
78
|
+
* this file most needs to avoid: the outcome log is the ONLY record of which
|
|
79
|
+
* worker actions a human actually blessed, so a row that vanishes on read
|
|
80
|
+
* looks exactly like a row nobody ever wrote — and the difference is whether
|
|
81
|
+
* a worker earned its autonomy. They are still skipped (a row with no key is
|
|
82
|
+
* genuinely unusable), but they are now COUNTED and reportable.
|
|
83
|
+
*/
|
|
84
|
+
export interface UnkeyableRow {
|
|
85
|
+
/** 1-based line number in the log, so an operator can go find it. */
|
|
86
|
+
line: number;
|
|
87
|
+
reason: "malformed-json" | "no-key" | "no-disposition" | "bad-ref";
|
|
88
|
+
/** First 120 chars of the offending line, for identification. */
|
|
89
|
+
excerpt: string;
|
|
90
|
+
}
|
|
57
91
|
/**
|
|
58
92
|
* Persist + query outcome dispositions for filed issues.
|
|
59
93
|
*
|
|
@@ -79,10 +113,28 @@ export declare class OutcomeStore {
|
|
|
79
113
|
* evidence). A filing with no recorded disposition can neither raise nor
|
|
80
114
|
* lower trust. (#1064)
|
|
81
115
|
*/
|
|
82
|
-
getDisposition(
|
|
116
|
+
getDisposition(key: string): OutcomeDisposition | null;
|
|
117
|
+
/**
|
|
118
|
+
* Disposition for a tool-scoped action reference (the non-URL join). Same
|
|
119
|
+
* null semantics as `getDisposition`. A ref that cannot be canonicalised
|
|
120
|
+
* returns null rather than throwing — this is a read path, and the fold
|
|
121
|
+
* must never crash on a malformed stored ref.
|
|
122
|
+
*/
|
|
123
|
+
getDispositionForRef(ref: ActionRef): OutcomeDisposition | null;
|
|
83
124
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
125
|
+
* Rows in the log that carry no usable lookup key (or no disposition, or
|
|
126
|
+
* are not valid JSON). Empty in the healthy case. Surfaced so an operator
|
|
127
|
+
* can be TOLD the ledger has holes instead of a confirmation quietly going
|
|
128
|
+
* missing — the whole point of counting these rather than skipping them.
|
|
129
|
+
*/
|
|
130
|
+
unkeyableRows(): UnkeyableRow[];
|
|
131
|
+
/**
|
|
132
|
+
* Persist a disposition. Later calls supersede earlier ones for the same
|
|
133
|
+
* key (both on disk via append, and in the shared in-memory cache).
|
|
134
|
+
*
|
|
135
|
+
* Throws when the record carries neither a usable `ref` nor an `issueUrl`:
|
|
136
|
+
* a row written with no key is invisible to every reader, so accepting it
|
|
137
|
+
* would silently discard an operator's explicit judgment.
|
|
86
138
|
*/
|
|
87
139
|
upsert(record: OutcomeRecord): void;
|
|
88
140
|
/** All records (deduped, last-writer-wins). For reporting / ingester diffing. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { AmbiguousActionRefError, canonicalActionRef, } from "./actionRef.js";
|
|
4
5
|
/**
|
|
5
6
|
* The directory that holds `outcome-log.jsonl`. Honors `PATCHWORK_HOME` (when
|
|
6
7
|
* set) so the WRITE path — `patchwork outcomes confirm|reject`, the
|
|
@@ -16,35 +17,66 @@ export function resolveOutcomeLogDir(override) {
|
|
|
16
17
|
return (override ?? process.env.PATCHWORK_HOME ?? path.join(homedir(), ".patchwork"));
|
|
17
18
|
}
|
|
18
19
|
const outcomeLogCache = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* The lookup key for a record: its `ref` (canonicalised) when present,
|
|
22
|
+
* otherwise its legacy `issueUrl`. Returns null when neither yields one.
|
|
23
|
+
*/
|
|
24
|
+
function keyForRecord(r) {
|
|
25
|
+
if (r.ref) {
|
|
26
|
+
try {
|
|
27
|
+
return canonicalActionRef(r.ref);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return r.issueUrl?.trim() || null;
|
|
34
|
+
}
|
|
19
35
|
function parseOutcomeLog(logPath) {
|
|
20
36
|
const dispositions = new Map();
|
|
21
37
|
const records = new Map();
|
|
38
|
+
const unkeyable = [];
|
|
22
39
|
if (!existsSync(logPath))
|
|
23
|
-
return { dispositions, records };
|
|
40
|
+
return { dispositions, records, unkeyable };
|
|
24
41
|
const text = readFileSync(logPath, "utf-8");
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
const lines = text.split("\n");
|
|
43
|
+
for (let i = 0; i < lines.length; i++) {
|
|
44
|
+
const t = (lines[i] ?? "").trim();
|
|
27
45
|
if (!t)
|
|
28
46
|
continue;
|
|
47
|
+
const excerpt = t.slice(0, 120);
|
|
48
|
+
let r;
|
|
29
49
|
try {
|
|
30
|
-
|
|
31
|
-
if (!r.issueUrl || !r.disposition)
|
|
32
|
-
continue;
|
|
33
|
-
// A manual disposition is sticky against a later ingester write for
|
|
34
|
-
// the same issueUrl — an automated poll must not silently erase an
|
|
35
|
-
// operator's explicit judgment call. A later manual write (the
|
|
36
|
-
// operator changing their own mind) always applies normally.
|
|
37
|
-
const existing = records.get(r.issueUrl);
|
|
38
|
-
if (existing?.origin === "manual" && r.origin !== "manual")
|
|
39
|
-
continue;
|
|
40
|
-
dispositions.set(r.issueUrl, r.disposition);
|
|
41
|
-
records.set(r.issueUrl, r);
|
|
50
|
+
r = JSON.parse(t);
|
|
42
51
|
}
|
|
43
52
|
catch {
|
|
44
|
-
|
|
53
|
+
unkeyable.push({ line: i + 1, reason: "malformed-json", excerpt });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!r.disposition) {
|
|
57
|
+
unkeyable.push({ line: i + 1, reason: "no-disposition", excerpt });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const key = keyForRecord(r);
|
|
61
|
+
if (!key) {
|
|
62
|
+
unkeyable.push({
|
|
63
|
+
line: i + 1,
|
|
64
|
+
reason: r.ref ? "bad-ref" : "no-key",
|
|
65
|
+
excerpt,
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
45
68
|
}
|
|
69
|
+
// A manual disposition is sticky against a later ingester write for
|
|
70
|
+
// the same key — an automated poll must not silently erase an
|
|
71
|
+
// operator's explicit judgment call. A later manual write (the
|
|
72
|
+
// operator changing their own mind) always applies normally.
|
|
73
|
+
const existing = records.get(key);
|
|
74
|
+
if (existing?.origin === "manual" && r.origin !== "manual")
|
|
75
|
+
continue;
|
|
76
|
+
dispositions.set(key, r.disposition);
|
|
77
|
+
records.set(key, r);
|
|
46
78
|
}
|
|
47
|
-
return { dispositions, records };
|
|
79
|
+
return { dispositions, records, unkeyable };
|
|
48
80
|
}
|
|
49
81
|
/** Fresh parse + fresh stat, always (used to (re)seed the shared cache). */
|
|
50
82
|
function loadOutcomeLogEntry(logPath) {
|
|
@@ -58,8 +90,8 @@ function loadOutcomeLogEntry(logPath) {
|
|
|
58
90
|
catch {
|
|
59
91
|
/* file absent — still cacheable at (-1, -1) */
|
|
60
92
|
}
|
|
61
|
-
const { dispositions, records } = parseOutcomeLog(logPath);
|
|
62
|
-
return { mtimeMs, size, dispositions, records };
|
|
93
|
+
const { dispositions, records, unkeyable } = parseOutcomeLog(logPath);
|
|
94
|
+
return { mtimeMs, size, dispositions, records, unkeyable };
|
|
63
95
|
}
|
|
64
96
|
/** The shared entry for `logPath`, reparsing only if the file actually changed. */
|
|
65
97
|
function getOutcomeLogEntry(logPath) {
|
|
@@ -108,14 +140,46 @@ export class OutcomeStore {
|
|
|
108
140
|
* evidence). A filing with no recorded disposition can neither raise nor
|
|
109
141
|
* lower trust. (#1064)
|
|
110
142
|
*/
|
|
111
|
-
getDisposition(
|
|
112
|
-
return getOutcomeLogEntry(this.logPath).dispositions.get(
|
|
143
|
+
getDisposition(key) {
|
|
144
|
+
return getOutcomeLogEntry(this.logPath).dispositions.get(key) ?? null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Disposition for a tool-scoped action reference (the non-URL join). Same
|
|
148
|
+
* null semantics as `getDisposition`. A ref that cannot be canonicalised
|
|
149
|
+
* returns null rather than throwing — this is a read path, and the fold
|
|
150
|
+
* must never crash on a malformed stored ref.
|
|
151
|
+
*/
|
|
152
|
+
getDispositionForRef(ref) {
|
|
153
|
+
let key;
|
|
154
|
+
try {
|
|
155
|
+
key = canonicalActionRef(ref);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return this.getDisposition(key);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Rows in the log that carry no usable lookup key (or no disposition, or
|
|
164
|
+
* are not valid JSON). Empty in the healthy case. Surfaced so an operator
|
|
165
|
+
* can be TOLD the ledger has holes instead of a confirmation quietly going
|
|
166
|
+
* missing — the whole point of counting these rather than skipping them.
|
|
167
|
+
*/
|
|
168
|
+
unkeyableRows() {
|
|
169
|
+
return getOutcomeLogEntry(this.logPath).unkeyable;
|
|
113
170
|
}
|
|
114
171
|
/**
|
|
115
|
-
* Persist a disposition
|
|
116
|
-
* (both on disk via append, and in the shared in-memory cache).
|
|
172
|
+
* Persist a disposition. Later calls supersede earlier ones for the same
|
|
173
|
+
* key (both on disk via append, and in the shared in-memory cache).
|
|
174
|
+
*
|
|
175
|
+
* Throws when the record carries neither a usable `ref` nor an `issueUrl`:
|
|
176
|
+
* a row written with no key is invisible to every reader, so accepting it
|
|
177
|
+
* would silently discard an operator's explicit judgment.
|
|
117
178
|
*/
|
|
118
179
|
upsert(record) {
|
|
180
|
+
if (!keyForRecord(record)) {
|
|
181
|
+
throw new AmbiguousActionRefError("An outcome record needs a usable key — either `issueUrl` or a `ref` with a tool and an id. Refusing to write a record nothing can look up.");
|
|
182
|
+
}
|
|
119
183
|
const line = `${JSON.stringify(record)}\n`;
|
|
120
184
|
appendFileSync(this.logPath, line, "utf-8");
|
|
121
185
|
// Re-seed the shared cache from the post-write file state so this write
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outcomeStore.js","sourceRoot":"","sources":["../../src/workers/outcomeStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"outcomeStore.js","sourceRoot":"","sources":["../../src/workers/outcomeStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const OUTCOMES_USAGE = `Usage:
|
|
2
|
-
patchwork outcomes confirm <issue-url>
|
|
3
|
-
patchwork outcomes
|
|
2
|
+
patchwork outcomes confirm <issue-url> [--recipe <name>] [--class <actionClass>]
|
|
3
|
+
patchwork outcomes confirm --tool <t> --id <id> [--recipe <name>] [--class <actionClass>]
|
|
4
|
+
patchwork outcomes reject <issue-url> [--recipe <name>] [--class <actionClass>]
|
|
5
|
+
patchwork outcomes reject --tool <t> --id <id> [--recipe <name>] [--class <actionClass>]
|
|
4
6
|
patchwork outcomes list [--json]
|
|
5
7
|
patchwork outcomes pending [--json]
|
|
6
8
|
|
|
@@ -14,6 +16,10 @@ cannot self-confirm its own filings.
|
|
|
14
16
|
list print all recorded outcomes (--json for raw output)
|
|
15
17
|
pending list filings still awaiting confirmation + the exact confirm command
|
|
16
18
|
|
|
19
|
+
--tool <tool> --id <id> reference an action that returned no URL, by the tool
|
|
20
|
+
that performed it plus the id it returned (e.g.
|
|
21
|
+
--tool todoist.create_task --id AaBbCcDdEeFfGgHh).
|
|
22
|
+
Both are required together; never mixed with a URL.
|
|
17
23
|
--recipe <name> stamp the filing recipe onto the record (audit context)
|
|
18
24
|
--class <actionClass> stamp the action class onto the record (audit context)
|
|
19
25
|
`;
|
|
@@ -41,33 +47,69 @@ export function runOutcomesCli(args, deps) {
|
|
|
41
47
|
return { stdout: formatOutcomeList(records), exitCode: 0 };
|
|
42
48
|
}
|
|
43
49
|
if (sub === "confirm" || sub === "reject") {
|
|
44
|
-
const
|
|
45
|
-
|
|
50
|
+
const positional = args[1];
|
|
51
|
+
const refTool = flagValue(args, "--tool");
|
|
52
|
+
const refId = flagValue(args, "--id");
|
|
53
|
+
const usingRef = refTool !== undefined || refId !== undefined;
|
|
54
|
+
// Never guess which form the operator meant. A bare string that happens
|
|
55
|
+
// to look like neither is an error, not something to coerce into a key
|
|
56
|
+
// nothing will look up.
|
|
57
|
+
if (usingRef && positional && !positional.startsWith("-")) {
|
|
46
58
|
return {
|
|
47
|
-
stderr: `Error:
|
|
59
|
+
stderr: `Error: pass EITHER an <issue-url> OR --tool/--id, not both (got "${positional}" alongside --tool/--id).\n`,
|
|
48
60
|
exitCode: 1,
|
|
49
61
|
};
|
|
50
62
|
}
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
if (usingRef) {
|
|
64
|
+
if (!refTool ||
|
|
65
|
+
!refId ||
|
|
66
|
+
refTool.startsWith("-") ||
|
|
67
|
+
refId.startsWith("-")) {
|
|
68
|
+
return {
|
|
69
|
+
stderr: `Error: --tool and --id must BOTH be given, with values (e.g. --tool todoist.create_task --id AaBbCcDdEeFfGgHh).\n`,
|
|
70
|
+
exitCode: 1,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
if (!positional || positional.startsWith("-")) {
|
|
76
|
+
return {
|
|
77
|
+
stderr: `Error: \`outcomes ${sub}\` requires an <issue-url>, or --tool <tool> --id <id> for an action with no URL.\n\n${OUTCOMES_USAGE}`,
|
|
78
|
+
exitCode: 1,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (!/^https?:\/\//.test(positional)) {
|
|
82
|
+
return {
|
|
83
|
+
stderr: `Error: <issue-url> must be an http(s) URL (got "${positional}"). Pass the issue's URL exactly as the worker filed it, or use --tool <tool> --id <id> for an action that has no URL.\n`,
|
|
84
|
+
exitCode: 1,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
56
87
|
}
|
|
57
88
|
const disposition = sub === "confirm" ? "confirmed" : "junk";
|
|
58
89
|
const recipeName = flagValue(args, "--recipe");
|
|
59
90
|
const workerClass = flagValue(args, "--class");
|
|
60
91
|
const record = {
|
|
61
|
-
|
|
92
|
+
...(usingRef
|
|
93
|
+
? { ref: { tool: refTool, id: refId } }
|
|
94
|
+
: { issueUrl: positional }),
|
|
62
95
|
disposition,
|
|
63
96
|
checkedAt: deps.now,
|
|
64
97
|
origin: "manual",
|
|
65
98
|
...(recipeName ? { recipeName } : {}),
|
|
66
99
|
...(workerClass ? { workerClass } : {}),
|
|
67
100
|
};
|
|
68
|
-
|
|
101
|
+
try {
|
|
102
|
+
deps.store.upsert(record);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
return {
|
|
106
|
+
stderr: `Error: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
107
|
+
exitCode: 1,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const what = usingRef ? `${refTool}:${refId}` : positional;
|
|
69
111
|
return {
|
|
70
|
-
stdout: `Recorded ${disposition} for ${
|
|
112
|
+
stdout: `Recorded ${disposition} for ${what}\n` +
|
|
71
113
|
"The trust ramp folds this on its next replay — run `patchwork workers shadow` to see the dial move.\n",
|
|
72
114
|
exitCode: 0,
|
|
73
115
|
};
|
|
@@ -85,7 +127,13 @@ export function formatOutcomeList(records) {
|
|
|
85
127
|
const lines = records.map((r) => {
|
|
86
128
|
const when = new Date(r.checkedAt).toISOString();
|
|
87
129
|
const ctx = [r.recipeName, r.workerClass].filter(Boolean).join(" · ");
|
|
88
|
-
|
|
130
|
+
// Either key shape renders; a row carrying neither cannot be written any
|
|
131
|
+
// more (upsert refuses it) but may exist in an older log, so it is shown
|
|
132
|
+
// as an explicit hole rather than a blank.
|
|
133
|
+
const what = r.ref
|
|
134
|
+
? `${r.ref.tool}:${r.ref.id}`
|
|
135
|
+
: (r.issueUrl ?? "(no key — unusable row)");
|
|
136
|
+
return ` ${r.disposition.padEnd(9)} ${what}${ctx ? ` (${ctx})` : ""} @ ${when}`;
|
|
89
137
|
});
|
|
90
138
|
return `${records.length} recorded outcome(s):\n${lines.join("\n")}\n`;
|
|
91
139
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outcomesCli.js","sourceRoot":"","sources":["../../src/workers/outcomesCli.ts"],"names":[],"mappings":"AA8BA,MAAM,cAAc,GAAG
|
|
1
|
+
{"version":3,"file":"outcomesCli.js","sourceRoot":"","sources":["../../src/workers/outcomesCli.ts"],"names":[],"mappings":"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"}
|
|
@@ -127,7 +127,29 @@ export declare function foldOutcome(step: FoldStep, runAt: number, opts: {
|
|
|
127
127
|
now?: number;
|
|
128
128
|
windowMs: number;
|
|
129
129
|
outcomeStore?: OutcomeStore;
|
|
130
|
+
/**
|
|
131
|
+
* Key the outcome join on tool-name + external id rather than on
|
|
132
|
+
* `output.url` alone. Default false = the historical URL-only behaviour.
|
|
133
|
+
* See the note at the join site below for why this is gated.
|
|
134
|
+
*/
|
|
135
|
+
strictOutcomeJoin?: boolean;
|
|
130
136
|
}): FoldDecision;
|
|
137
|
+
/**
|
|
138
|
+
* How `foldOutcome` would label this step under BOTH join rules — the measured
|
|
139
|
+
* blast radius of turning `strictOutcomeJoin` on, without turning it on.
|
|
140
|
+
*
|
|
141
|
+
* Returns null when the two agree (the overwhelmingly common case, and the
|
|
142
|
+
* only case an operator does not need to see).
|
|
143
|
+
*/
|
|
144
|
+
export declare function foldJoinDelta(step: FoldStep, runAt: number, opts: {
|
|
145
|
+
now?: number;
|
|
146
|
+
windowMs: number;
|
|
147
|
+
outcomeStore?: OutcomeStore;
|
|
148
|
+
}): {
|
|
149
|
+
tool: string;
|
|
150
|
+
lenient: FoldDecision;
|
|
151
|
+
strict: FoldDecision;
|
|
152
|
+
} | null;
|
|
131
153
|
export declare class WorkerShadowObserver {
|
|
132
154
|
private readonly store;
|
|
133
155
|
private readonly cfg;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { categoriseHaltReason } from "../recipes/haltCategory.js";
|
|
2
2
|
import { classifyActionClass } from "./actionClass.js";
|
|
3
|
+
import { deriveActionKey } from "./actionRef.js";
|
|
3
4
|
import { DEFAULT_GRADUATION_CONFIG, } from "./graduation.js";
|
|
4
5
|
import { recommend } from "./shadowGate.js";
|
|
5
6
|
import { ownsAction, ownsClassKey, priorFor, } from "./worker.js";
|
|
@@ -40,10 +41,28 @@ export function foldOutcome(step, runAt, opts) {
|
|
|
40
41
|
// For a non-reversible filing, read the operator disposition up front (issue /
|
|
41
42
|
// PR URL). Reversible successes never consult the store — they are always durable.
|
|
42
43
|
const outcomeStore = ac.reversibility !== "reversible" ? opts.outcomeStore : undefined;
|
|
44
|
+
// The join key for this action. Legacy behaviour keyed on `output.url`
|
|
45
|
+
// alone, which meant any write tool returning no URL skipped the human
|
|
46
|
+
// confirmation check entirely and fell through to good:true below — trust
|
|
47
|
+
// for work nobody ever looked at. `deriveActionKey` generalises this to
|
|
48
|
+
// tool-name + external id (see actionRef.ts), which every write tool has.
|
|
49
|
+
//
|
|
50
|
+
// GATED, on purpose. Turning the generalisation on makes previously-
|
|
51
|
+
// unjoinable successes reachable by the WITHHOLD branch below, which
|
|
52
|
+
// de-rates every worker that earned trust from a non-URL action. That is
|
|
53
|
+
// the correct end state, but it is a live change to the dial the gate reads,
|
|
54
|
+
// so it ships measured rather than assumed: `strictOutcomeJoin` defaults to
|
|
55
|
+
// false (byte-identical to the URL-only behaviour) and the shadow report
|
|
56
|
+
// counts what WOULD change. Flipping the default is a separate change.
|
|
43
57
|
const url = outcomeStore && step.output && typeof step.output.url === "string"
|
|
44
58
|
? step.output.url
|
|
45
59
|
: null;
|
|
46
|
-
const
|
|
60
|
+
const key = opts.strictOutcomeJoin
|
|
61
|
+
? outcomeStore
|
|
62
|
+
? deriveActionKey(step.tool, step.output)
|
|
63
|
+
: null
|
|
64
|
+
: url;
|
|
65
|
+
const disposition = key ? (outcomeStore?.getDisposition(key) ?? null) : null;
|
|
47
66
|
// A human REJECTION demotes IMMEDIATELY. Junk is durable evidence of failure
|
|
48
67
|
// the moment it lands, so — like any outright failure (status ≠ ok above) — it
|
|
49
68
|
// must NOT sit withheld for the durability window ("demotion is instant", see
|
|
@@ -56,10 +75,29 @@ export function foldOutcome(step, runAt, opts) {
|
|
|
56
75
|
return { fold: false }; // pending — survives the window before it earns trust
|
|
57
76
|
// Past the window: an unactioned filing (unknown / no record) is WITHHELD —
|
|
58
77
|
// unactioned ≠ correct (trust-by-neglect fix). Confirmed / no-url → good:true.
|
|
59
|
-
if (
|
|
78
|
+
if (key && (disposition === "unknown" || disposition === null))
|
|
60
79
|
return { fold: false }; // withheld — not evidence
|
|
61
80
|
return { fold: true, good: true };
|
|
62
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* How `foldOutcome` would label this step under BOTH join rules — the measured
|
|
84
|
+
* blast radius of turning `strictOutcomeJoin` on, without turning it on.
|
|
85
|
+
*
|
|
86
|
+
* Returns null when the two agree (the overwhelmingly common case, and the
|
|
87
|
+
* only case an operator does not need to see).
|
|
88
|
+
*/
|
|
89
|
+
export function foldJoinDelta(step, runAt, opts) {
|
|
90
|
+
const lenient = foldOutcome(step, runAt, {
|
|
91
|
+
...opts,
|
|
92
|
+
strictOutcomeJoin: false,
|
|
93
|
+
});
|
|
94
|
+
const strict = foldOutcome(step, runAt, { ...opts, strictOutcomeJoin: true });
|
|
95
|
+
const same = lenient.fold === strict.fold &&
|
|
96
|
+
(!lenient.fold || !strict.fold || lenient.good === strict.good);
|
|
97
|
+
if (same)
|
|
98
|
+
return null;
|
|
99
|
+
return { tool: step.tool ?? "(none)", lenient, strict };
|
|
100
|
+
}
|
|
63
101
|
export class WorkerShadowObserver {
|
|
64
102
|
store;
|
|
65
103
|
cfg;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shadowObserver.js","sourceRoot":"","sources":["../../src/workers/shadowObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAsB,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EACL,yBAAyB,GAE1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,YAAY,EACZ,QAAQ,GAET,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAoF/B;;;oBAGoB;AACpB,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhE;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAC9B,aAA4B,EAC5B,KAAa,EACb,GAAW,EACX,QAAgB;IAEhB,IAAI,aAAa,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,KAAK,IAAI,GAAG,GAAG,QAAQ,CAAC;AACjC,CAAC;AA0BD,MAAM,UAAU,WAAW,CACzB,IAAc,EACd,KAAa,EACb,
|
|
1
|
+
{"version":3,"file":"shadowObserver.js","sourceRoot":"","sources":["../../src/workers/shadowObserver.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchwork-os",
|
|
3
|
-
"version": "1.1.0-beta.4.canary.
|
|
3
|
+
"version": "1.1.0-beta.4.canary.554",
|
|
4
4
|
"description": "Your personal AI runtime, local-first. Patchwork OS gives any AI model a consistent set of tools, YAML recipes, a delegation policy with approval queue, and a durable trace memory — all on your machine, all under your policy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|