@bevel-software/platform-core-backend 0.12.0 → 0.12.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/THIRD-PARTY-NOTICES.md +4 -4
- package/dist/modules/kb-fs/clone-config.d.ts +40 -2
- package/dist/modules/kb-fs/clone-config.d.ts.map +1 -1
- package/dist/modules/kb-fs/clone-config.js +94 -2
- package/dist/modules/kb-fs/clone-config.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +38 -0
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +112 -6
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +21 -4
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +52 -8
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +121 -23
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/package.json +5 -5
- package/src/modules/kb-fs/__tests__/clone-config.test.ts +63 -2
- package/src/modules/kb-fs/clone-config.ts +97 -2
- package/src/modules/secrets-vault/secrets-vault.routes.ts +582 -582
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +11 -5
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +172 -7
- package/src/modules/workflow/workflow.service.ts +118 -6
- package/src/modules/workspace/__tests__/workspace.service.test.ts +1 -1
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +141 -0
- package/src/modules/workspace/startup/kb-git.ts +20 -7
- package/src/modules/workspace/workspace.service.ts +132 -25
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -26,10 +26,10 @@ These are shippable alongside our own licence, but carry an obligation beyond
|
|
|
26
26
|
attribution: modifications to *their own source files* must remain under the same
|
|
27
27
|
licence and be made available. Using them as dependencies triggers nothing.
|
|
28
28
|
|
|
29
|
-
- `@utcp/code-mode@1.2.
|
|
29
|
+
- `@utcp/code-mode@1.2.13` — MPL-2.0
|
|
30
30
|
- `@utcp/direct-call@1.1.1` — MPL-2.0
|
|
31
31
|
- `@utcp/http@1.1.11` — MPL-2.0
|
|
32
|
-
- `@utcp/mcp@1.1.
|
|
32
|
+
- `@utcp/mcp@1.1.6` — MPL-2.0
|
|
33
33
|
- `@utcp/sdk@1.1.2` — MPL-2.0
|
|
34
34
|
|
|
35
35
|
## Packages shipping no licence file
|
|
@@ -84,10 +84,10 @@ the declared licence; refer to the upstream project for the notice.
|
|
|
84
84
|
- **@types/node@22.20.1** — MIT — see licence text #16 <https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node>
|
|
85
85
|
- **@types/pg@8.20.0** — MIT — see licence text #16 <https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/pg>
|
|
86
86
|
- **@types/unist@3.0.3** — MIT — see licence text #16 <https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/unist>
|
|
87
|
-
- **@utcp/code-mode@1.2.
|
|
87
|
+
- **@utcp/code-mode@1.2.13** — MPL-2.0 — see licence text #17 <https://github.com/universal-tool-calling-protocol/code-mode#readme>
|
|
88
88
|
- **@utcp/direct-call@1.1.1** — MPL-2.0 — see licence text #17 <https://github.com/universal-tool-calling-protocol/typescript-utcp#readme>
|
|
89
89
|
- **@utcp/http@1.1.11** — MPL-2.0 — see licence text #17 <https://github.com/universal-tool-calling-protocol/typescript-utcp#readme>
|
|
90
|
-
- **@utcp/mcp@1.1.
|
|
90
|
+
- **@utcp/mcp@1.1.6** — MPL-2.0 — see licence text #17 <https://github.com/universal-tool-calling-protocol/typescript-utcp#readme>
|
|
91
91
|
- **@utcp/sdk@1.1.2** — MPL-2.0 — see licence text #17 <https://github.com/universal-tool-calling-protocol/typescript-utcp#readme>
|
|
92
92
|
- **@vercel/oidc@3.2.0** — Apache-2.0 — see licence text #18 <https://vercel.com>
|
|
93
93
|
- **@workflow/serde@4.1.0-beta.2** — Apache-2.0 — see licence text #19 <https://github.com/vercel/workflow#readme>
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The git configuration a per-branch workspace clone must
|
|
3
|
-
* to
|
|
2
|
+
* The git configuration a per-branch workspace clone must carry: the tracking
|
|
3
|
+
* config that pins its branch to exactly ONE upstream ref, and the credential
|
|
4
|
+
* helper its remote operations authenticate with.
|
|
5
|
+
*
|
|
6
|
+
* Both are stamped at clone time AND re-stamped when an existing clone is
|
|
7
|
+
* adopted from disk, so a clone created by an older build — or by a path that
|
|
8
|
+
* only authenticated its own invocation — is repaired rather than left broken.
|
|
9
|
+
*
|
|
10
|
+
* On the tracking half:
|
|
4
11
|
*
|
|
5
12
|
* Why this exists: a clone's config is append-only from git's point of view,
|
|
6
13
|
* and several things write to it:
|
|
@@ -58,4 +65,35 @@ export declare const SAFE_IMPLICIT_FETCH_ARGS: readonly string[];
|
|
|
58
65
|
* workspace service) — no process is spawned here.
|
|
59
66
|
*/
|
|
60
67
|
export declare function cloneTrackingConfigArgs(branch: string): string[][];
|
|
68
|
+
/**
|
|
69
|
+
* The inline credential helper a workspace clone authenticates its remote
|
|
70
|
+
* operations with, or null when the deployment has no git token configured.
|
|
71
|
+
*
|
|
72
|
+
* The snippet reads `$GITHUB_TOKEN` at CALL time, so the secret itself never
|
|
73
|
+
* enters argv, the clone's `.git/config`, or any error message quoting either.
|
|
74
|
+
* `GITHUB_TOKEN` is the normalised name — `CoreConfig` collapses `GIT_TOKEN` /
|
|
75
|
+
* `GH_TOKEN` onto it at boot, and `DeploymentSettingsService.syncGitTokenEnv`
|
|
76
|
+
* publishes a token supplied through the setup screen to the same place — so
|
|
77
|
+
* this one name covers every way a token can arrive.
|
|
78
|
+
*
|
|
79
|
+
* The username is provider-specific (GitHub `x-access-token`, GitLab `oauth2`,
|
|
80
|
+
* Bitbucket `x-token-auth`); the token is always the Basic-auth password,
|
|
81
|
+
* which every major host accepts.
|
|
82
|
+
*/
|
|
83
|
+
export declare function credentialHelperValue(gitUsername: string): string | null;
|
|
84
|
+
/**
|
|
85
|
+
* `git clone` arguments that PERSIST the credential helper into the repository
|
|
86
|
+
* being created. Position is load-bearing: these belong AFTER the `clone`
|
|
87
|
+
* subcommand, where `--config` means "write this into the new repo's config".
|
|
88
|
+
* The same pair placed BEFORE it is git's per-invocation `-c`, which
|
|
89
|
+
* authenticates that one clone and leaves the resulting repo with no helper at
|
|
90
|
+
* all — so every later `git push` from that clone prompts for a username,
|
|
91
|
+
* finds no tty, and dies. That was a real outage: a self-hosted deployment
|
|
92
|
+
* cloned fine, committed fine, and silently pushed nothing for two days.
|
|
93
|
+
*
|
|
94
|
+
* Empty when no token is configured, matching the un-authenticated clone the
|
|
95
|
+
* rest of the layer already supports.
|
|
96
|
+
*/
|
|
97
|
+
export declare function cloneCredentialArgs(gitUsername: string): string[];
|
|
98
|
+
export declare function cloneCredentialConfigArgs(gitUsername: string): string[][];
|
|
61
99
|
//# sourceMappingURL=clone-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clone-config.d.ts","sourceRoot":"","sources":["../../../src/modules/kb-fs/clone-config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"clone-config.d.ts","sourceRoot":"","sources":["../../../src/modules/kb-fs/clone-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,wCAAwC,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAErD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAMlE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBxE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAGjE;AA2BD,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CASzE"}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The git configuration a per-branch workspace clone must
|
|
3
|
-
* to
|
|
2
|
+
* The git configuration a per-branch workspace clone must carry: the tracking
|
|
3
|
+
* config that pins its branch to exactly ONE upstream ref, and the credential
|
|
4
|
+
* helper its remote operations authenticate with.
|
|
5
|
+
*
|
|
6
|
+
* Both are stamped at clone time AND re-stamped when an existing clone is
|
|
7
|
+
* adopted from disk, so a clone created by an older build — or by a path that
|
|
8
|
+
* only authenticated its own invocation — is repaired rather than left broken.
|
|
9
|
+
*
|
|
10
|
+
* On the tracking half:
|
|
4
11
|
*
|
|
5
12
|
* Why this exists: a clone's config is append-only from git's point of view,
|
|
6
13
|
* and several things write to it:
|
|
@@ -66,4 +73,89 @@ export function cloneTrackingConfigArgs(branch) {
|
|
|
66
73
|
['config', '--replace-all', `branch.${branch}.merge`, `refs/heads/${branch}`],
|
|
67
74
|
];
|
|
68
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* The inline credential helper a workspace clone authenticates its remote
|
|
78
|
+
* operations with, or null when the deployment has no git token configured.
|
|
79
|
+
*
|
|
80
|
+
* The snippet reads `$GITHUB_TOKEN` at CALL time, so the secret itself never
|
|
81
|
+
* enters argv, the clone's `.git/config`, or any error message quoting either.
|
|
82
|
+
* `GITHUB_TOKEN` is the normalised name — `CoreConfig` collapses `GIT_TOKEN` /
|
|
83
|
+
* `GH_TOKEN` onto it at boot, and `DeploymentSettingsService.syncGitTokenEnv`
|
|
84
|
+
* publishes a token supplied through the setup screen to the same place — so
|
|
85
|
+
* this one name covers every way a token can arrive.
|
|
86
|
+
*
|
|
87
|
+
* The username is provider-specific (GitHub `x-access-token`, GitLab `oauth2`,
|
|
88
|
+
* Bitbucket `x-token-auth`); the token is always the Basic-auth password,
|
|
89
|
+
* which every major host accepts.
|
|
90
|
+
*/
|
|
91
|
+
export function credentialHelperValue(gitUsername) {
|
|
92
|
+
if (!process.env.GITHUB_TOKEN)
|
|
93
|
+
return null;
|
|
94
|
+
// Interpolated into a shell snippet that git will execute. `CoreConfig` and
|
|
95
|
+
// `DeploymentSettingsService` both reject a username outside this charset
|
|
96
|
+
// before it can get this far, so reaching the throw means an unvalidated
|
|
97
|
+
// fourth path appeared — fail closed rather than emit an injectable helper.
|
|
98
|
+
if (!/^[A-Za-z0-9._-]+$/.test(gitUsername)) {
|
|
99
|
+
throw new Error(`git username must match [A-Za-z0-9._-]+; got "${gitUsername}"`);
|
|
100
|
+
}
|
|
101
|
+
// printf '%s' rather than echo: POSIX leaves echo's handling of backslash
|
|
102
|
+
// escapes implementation-defined (dash interprets \c as "stop output"), so
|
|
103
|
+
// a token containing a backslash sequence could be silently truncated on
|
|
104
|
+
// the way to git. printf %s passes the bytes through verbatim. The literal
|
|
105
|
+
// `password=$GITHUB_TOKEN` stays in the snippet, which is what
|
|
106
|
+
// APP_HELPER_VALUE_PATTERN keys on — echo-era helpers stamped by earlier
|
|
107
|
+
// builds still match it and get replaced on the next stamp.
|
|
108
|
+
return `!f() { printf '%s\\n' "username=${gitUsername}" "password=$GITHUB_TOKEN"; }; f`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `git clone` arguments that PERSIST the credential helper into the repository
|
|
112
|
+
* being created. Position is load-bearing: these belong AFTER the `clone`
|
|
113
|
+
* subcommand, where `--config` means "write this into the new repo's config".
|
|
114
|
+
* The same pair placed BEFORE it is git's per-invocation `-c`, which
|
|
115
|
+
* authenticates that one clone and leaves the resulting repo with no helper at
|
|
116
|
+
* all — so every later `git push` from that clone prompts for a username,
|
|
117
|
+
* finds no tty, and dies. That was a real outage: a self-hosted deployment
|
|
118
|
+
* cloned fine, committed fine, and silently pushed nothing for two days.
|
|
119
|
+
*
|
|
120
|
+
* Empty when no token is configured, matching the un-authenticated clone the
|
|
121
|
+
* rest of the layer already supports.
|
|
122
|
+
*/
|
|
123
|
+
export function cloneCredentialArgs(gitUsername) {
|
|
124
|
+
const helper = credentialHelperValue(gitUsername);
|
|
125
|
+
return helper ? ['--config', `credential.helper=${helper}`] : [];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* `git` argument lists that bring a clone's credential helper into line with
|
|
129
|
+
* the deployment's current config — the repair path for clones on disk (see
|
|
130
|
+
* `cloneCredentialArgs`), applied wherever `cloneTrackingConfigArgs` is:
|
|
131
|
+
* `--replace-all` when a token is configured (idempotent re-stamp, stale
|
|
132
|
+
* values collapse), `--unset-all` when the deployment lost its token (a
|
|
133
|
+
* leftover helper answering with an empty password must not shadow whatever
|
|
134
|
+
* auth the operator fell back to; unset of a missing value exits non-zero
|
|
135
|
+
* and callers tolerate it — see `stampCredentialHelper`). Both operations
|
|
136
|
+
* are scoped by the value pattern below so only app-owned helpers are
|
|
137
|
+
* touched. Returns the arguments rather than running them, for the same
|
|
138
|
+
* reason `cloneTrackingConfigArgs` does.
|
|
139
|
+
*/
|
|
140
|
+
/**
|
|
141
|
+
* Value pattern (a POSIX ERE, as `git config`'s optional value-pattern
|
|
142
|
+
* argument expects) that matches ONLY helpers this application stamped —
|
|
143
|
+
* every one of ours reads `password=$GITHUB_TOKEN`, and no operator-authored
|
|
144
|
+
* helper has a reason to contain that literal. Scoping both the replace and
|
|
145
|
+
* the unset to it means an operator's own clone-local helper (`store`,
|
|
146
|
+
* `cache`, something custom) is never collapsed away or deleted by our
|
|
147
|
+
* stamping: git chains all configured helpers in order, so ours and theirs
|
|
148
|
+
* coexist.
|
|
149
|
+
*/
|
|
150
|
+
const APP_HELPER_VALUE_PATTERN = 'password=\\$GITHUB_TOKEN';
|
|
151
|
+
export function cloneCredentialConfigArgs(gitUsername) {
|
|
152
|
+
const helper = credentialHelperValue(gitUsername);
|
|
153
|
+
// `--replace-all key value pattern` replaces every line MATCHING the
|
|
154
|
+
// pattern with the one value (adding it when none match) and leaves
|
|
155
|
+
// non-matching values — operator helpers — untouched. Same scoping on the
|
|
156
|
+
// unset: only app-stamped values are removed when the token goes away.
|
|
157
|
+
return helper
|
|
158
|
+
? [['config', '--replace-all', 'credential.helper', helper, APP_HELPER_VALUE_PATTERN]]
|
|
159
|
+
: [['config', '--unset-all', 'credential.helper', APP_HELPER_VALUE_PATTERN]];
|
|
160
|
+
}
|
|
69
161
|
//# sourceMappingURL=clone-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clone-config.js","sourceRoot":"","sources":["../../../src/modules/kb-fs/clone-config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"clone-config.js","sourceRoot":"","sources":["../../../src/modules/kb-fs/clone-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,4EAA4E;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,qCAAqC,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,QAAQ;CACtD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,OAAO;QACL,CAAC,QAAQ,EAAE,eAAe,EAAE,qBAAqB,EAAE,oBAAoB,CAAC;QACxE,CAAC,QAAQ,EAAE,eAAe,EAAE,UAAU,MAAM,SAAS,EAAE,QAAQ,CAAC;QAChE,CAAC,QAAQ,EAAE,eAAe,EAAE,UAAU,MAAM,QAAQ,EAAE,cAAc,MAAM,EAAE,CAAC;KAC9E,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,WAAmB;IACvD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAC3C,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,4EAA4E;IAC5E,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,iDAAiD,WAAW,GAAG,CAAC,CAAC;IACnF,CAAC;IACD,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,+DAA+D;IAC/D,yEAAyE;IACzE,4DAA4D;IAC5D,OAAO,mCAAmC,WAAW,kCAAkC,CAAC;AAC1F,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,MAAM,MAAM,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,qBAAqB,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH;;;;;;;;;GASG;AACH,MAAM,wBAAwB,GAAG,0BAA0B,CAAC;AAE5D,MAAM,UAAU,yBAAyB,CAAC,WAAmB;IAC3D,MAAM,MAAM,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAClD,qEAAqE;IACrE,oEAAoE;IACpE,0EAA0E;IAC1E,uEAAuE;IACvE,OAAO,MAAM;QACX,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,EAAE,wBAAwB,CAAC,CAAC;QACtF,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,mBAAmB,EAAE,wBAAwB,CAAC,CAAC,CAAC;AACjF,CAAC"}
|
|
@@ -297,6 +297,44 @@ export declare class WorkflowService implements IWorkflowService {
|
|
|
297
297
|
systemAuthorized?: boolean;
|
|
298
298
|
skipValidation?: boolean;
|
|
299
299
|
}): Promise<void>;
|
|
300
|
+
/**
|
|
301
|
+
* Workspaces whose last push failed. Purely so the recovery event fires on
|
|
302
|
+
* the transition back to healthy instead of on every subsequent save — a
|
|
303
|
+
* push runs on every write, and the healthy case must stay silent.
|
|
304
|
+
*
|
|
305
|
+
* Process-local and deliberately not persisted: a restart clears it, and the
|
|
306
|
+
* worst that costs is one redundant `git-sync-recovered` on a workspace that
|
|
307
|
+
* was already fine, which clients treat as a no-op.
|
|
308
|
+
*/
|
|
309
|
+
private readonly gitSyncFailing;
|
|
310
|
+
/**
|
|
311
|
+
* Announce that a push landed. Emits only when this workspace was previously
|
|
312
|
+
* failing — see `gitSyncFailing`.
|
|
313
|
+
*/
|
|
314
|
+
private noteGitSyncOk;
|
|
315
|
+
/**
|
|
316
|
+
* Announce that a commit is sitting locally because its push failed.
|
|
317
|
+
*
|
|
318
|
+
* This is the visibility half of the failure paths below; it never changes
|
|
319
|
+
* what they DO. The commit is already safe on disk and the retry / hand-off
|
|
320
|
+
* machinery is unaffected — all this adds is that someone finds out, rather
|
|
321
|
+
* than the deployment looking healthy while nothing reaches the remote.
|
|
322
|
+
*/
|
|
323
|
+
private noteGitSyncFailed;
|
|
324
|
+
/**
|
|
325
|
+
* A bare push wrapped in the sync tracker: a failure raises the banner, a
|
|
326
|
+
* success clears it. For the workflow paths that push directly — sharing a
|
|
327
|
+
* branch, publishing a CR's source, update-from-base, the decline revert,
|
|
328
|
+
* the roles.yaml restore — rather than through `pushWithRecovery`'s
|
|
329
|
+
* cooperative ladder. Behaviour is otherwise unchanged: the error still
|
|
330
|
+
* propagates to the caller exactly as the bare push's did. Without this,
|
|
331
|
+
* those pushes could fail invisibly (no banner) and, worse, a successful
|
|
332
|
+
* share could not CLEAR a banner an earlier save had raised.
|
|
333
|
+
*
|
|
334
|
+
* The branch is derived from the workspace id (they are the same string,
|
|
335
|
+
* URL-encoding aside) so call sites cannot pass a mismatched pair.
|
|
336
|
+
*/
|
|
337
|
+
private trackedPush;
|
|
300
338
|
/**
|
|
301
339
|
* Shared implementation of "push, and on non-fast-forward try a
|
|
302
340
|
* cooperative pull-rebase + retry, then hand off to the agent if that
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow.service.d.ts","sourceRoot":"","sources":["../../../src/modules/workflow/workflow.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,KAAK,EACV,iBAAiB,EACjB,QAAQ,EACR,MAAM,EACN,qBAAqB,EACrB,yBAAyB,EACzB,MAAM,EACN,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,yBAAyB,EACzB,sBAAsB,EACtB,6BAA6B,EAC9B,MAAM,iCAAiC,CAAC;AAGzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AAC7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow.service.d.ts","sourceRoot":"","sources":["../../../src/modules/workflow/workflow.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,KAAK,EACV,iBAAiB,EACjB,QAAQ,EACR,MAAM,EACN,qBAAqB,EACrB,yBAAyB,EACzB,MAAM,EACN,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,yBAAyB,EACzB,sBAAsB,EACtB,6BAA6B,EAC9B,MAAM,iCAAiC,CAAC;AAGzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AAC7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAoDpD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,KAAK,eAAe,GAAG,GAAG,CACxB,MAAM,EACN;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAC9D,CAAC;AAKF;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,eAAe,EACzB,SAAS,GAAE,MAAkC,GAC5C,MAAM,CAoCR;AAED,qBAAa,eAAgB,YAAW,gBAAgB;IAqBpD,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAC7B;;;;;;;;OAQG;aACa,KAAK,EAAE,aAAa;IA3DtC;;;;;;;;;;OAUG;IACH;;;;;;;OAOG;gBAEgB,EAAE,EAAE,QAAQ,EACZ,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,kBAAkB,EACvB,cAAc,EAAE,sBAAsB,EACtC,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,cAAc,EAC7B,SAAS,EAAE,eAAe;IAC3C;;;;;;OAMG;IACc,cAAc,EAAE,qBAAqB,EACrC,SAAS,EAAE,MAAM;IAClC;;;;;OAKG;IACc,MAAM,CAAC,EAAE,gBAAgB,YAAA;IAC1C;;;;;OAKG;IACc,WAAW,CAAC,EAAE,kBAAkB,YAAA;IACjD;;;;;;;;OAQG;IACa,KAAK,GAAE,aAAmC;IAK5D;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAAS;IAE7B,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAqB3F,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAInF;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAwB;IAElD,YAAY,CAChB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3D,OAAO,CAAC,IAAI,CAAC;IA0BhB,yEAAyE;YAC3D,oBAAoB;IAkClC,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAOjE,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5C,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAW3E;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;YACW,yBAAyB;IA0BvC,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjE,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAI5E,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI3F,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAS9D,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAItF;;;;;;;;OAQG;IACG,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAczB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAIxF,WAAW,CACT,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;IAIlB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjF;;;;OAIG;IACH,YAAY,CACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAI/D;;;;;;;;;;OAUG;YACW,oBAAoB;IAwC5B,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAChC,OAAO,CAAC,iBAAiB,CAAC;IAwDvB,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,QAAQ,CAAC;IAepB;;;;;;OAMG;IACG,qBAAqB,CACzB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA2FzB;;;;;;;;OAQG;IACH;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC;IAwEhB;;;;;;;;;OASG;IACG,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,GAC9D,OAAO,CAAC,IAAI,CAAC;IA8ChB;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD;;;OAGG;IACH,OAAO,CAAC,aAAa;IAarB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;;;;;OAYG;YACW,WAAW;IAkBzB;;;;;OAKG;YACW,gBAAgB;IAoD9B;;;;;;;;OAQG;IACG,mBAAmB,CACvB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC;IA0FhB;;;;;;;;;OASG;IACG,oBAAoB,CACxB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,IAAI,CAAC;IAyBhB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1F,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIzD,OAAO,CACL,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAM3B,kBAAkB,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAIxE,4BAA4B,CAC1B,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzB,OAAO,CAAC,aAAa,EAAE,CAAC;IAI3B,yBAAyB,CACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzB,OAAO,CAAC,aAAa,EAAE,CAAC;IAI3B,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAI/D,sBAAsB,CACpB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GACrE,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAItC;;;;;;;;;;;;;;;;;;OAkBG;IACG,iBAAiB,CACrB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,mBAAmB,CAAC;IAwK/B;;;;;;;;;;;OAWG;IACG,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,CAAC;IA0C/B,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAIvD,WAAW,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,6BAA6B,EACpC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,CAAC;IAM1B,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,oBAAoB,CAAC;IAM1B,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/E,WAAW,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC;IAsBpB,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB1B;;;;;;;;;;;;;;;;OAgBG;IACG,uBAAuB,CAC3B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA4EzD;;;;;;;;OAQG;IACG,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAuCxE;;;;;;;;;;;;;;OAcG;IACG,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IA+B/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACG,sCAAsC,IAAI,OAAO,CAAC,MAAM,CAAC;IA8C/D;;;;;;;;;;;;;;;OAeG;IACG,mBAAmB,CACvB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,kBAAkB,EACzB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,yBAAyB,CAAC;IA8CrC;;;;;;;;;;OAUG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,YAAY,EAAE,EACzB,KAAK,EAAE,kBAAkB,EACzB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAC1B,OAAO,CAAC,yBAAyB,CAAC;IAiGrC;;;;;;;;;;;;;;;OAeG;YACW,wBAAwB;IA+CtC;;;;;;;;;;;OAWG;YACW,qBAAqB;IAiHnC;;;;;;OAMG;YACW,+BAA+B;IAqB7C;;;;;;;;;;;;OAYG;YACW,wBAAwB;CAwBvC"}
|
|
@@ -23,7 +23,8 @@ import { promisify } from 'node:util';
|
|
|
23
23
|
import { isProtectedBranch, DEFAULT_BRANCH } from '@bevel-software/platform-shared';
|
|
24
24
|
import { and, eq } from 'drizzle-orm';
|
|
25
25
|
import { changeRequests } from '../database/schema.js';
|
|
26
|
-
import { workspaceIdForBranch } from '../../shared/workspace-id.js';
|
|
26
|
+
import { workspaceIdForBranch, branchForWorkspaceId } from '../../shared/workspace-id.js';
|
|
27
|
+
import { sanitizeError } from './sanitize-error.js';
|
|
27
28
|
import { WorkflowHooks } from './workflow-hooks.js';
|
|
28
29
|
import { WorkspaceMutex } from '../kb-fs/mutex.js';
|
|
29
30
|
import { hashEmail } from '../../shared/hash-email.js';
|
|
@@ -285,7 +286,7 @@ export class WorkflowService {
|
|
|
285
286
|
// `discardWorkingChanges` removed: under save=share there's nothing dirty
|
|
286
287
|
// to discard.
|
|
287
288
|
shareCurrentBranch(workspaceId, user) {
|
|
288
|
-
return this.
|
|
289
|
+
return this.trackedPush(workspaceId, user);
|
|
289
290
|
}
|
|
290
291
|
refreshRemotes(workspaceId) {
|
|
291
292
|
return this.git.fetch(workspaceId);
|
|
@@ -528,6 +529,7 @@ export class WorkflowService {
|
|
|
528
529
|
if (change) {
|
|
529
530
|
try {
|
|
530
531
|
await this.git.push(workspaceId, user);
|
|
532
|
+
this.noteGitSyncOk(workspaceId, branch);
|
|
531
533
|
}
|
|
532
534
|
catch (err) {
|
|
533
535
|
// Non-fast-forward recovery on the autosave path: try the
|
|
@@ -544,17 +546,34 @@ export class WorkflowService {
|
|
|
544
546
|
const detail = err instanceof Error ? err.message : String(err);
|
|
545
547
|
const looksLikeNonFastForward = /non-fast-forward|rejected|fetch first|updates were rejected/i.test(detail);
|
|
546
548
|
let recovered = false;
|
|
549
|
+
let recoveryError = null;
|
|
547
550
|
if (looksLikeNonFastForward) {
|
|
548
551
|
try {
|
|
549
552
|
await this.git.pull(workspaceId);
|
|
550
553
|
await this.git.push(workspaceId, user);
|
|
551
554
|
recovered = true;
|
|
555
|
+
this.noteGitSyncOk(workspaceId, branch);
|
|
552
556
|
}
|
|
553
557
|
catch (recoveryErr) {
|
|
558
|
+
recoveryError = recoveryErr;
|
|
554
559
|
console.warn('[workflow] autosave cooperative recovery (pull-rebase) failed; leaving the unpushed commit for the next save / releaseLock to surface:', recoveryErr instanceof Error ? recoveryErr.message : recoveryErr);
|
|
555
560
|
}
|
|
556
561
|
}
|
|
557
562
|
if (!recovered) {
|
|
563
|
+
// Still best-effort — we don't fail the autosave. What gets the
|
|
564
|
+
// BANNER is only the class that cannot clear itself: auth or host
|
|
565
|
+
// failures, which no later attempt fixes. A non-fast-forward here —
|
|
566
|
+
// recovery attempted or not — is the routine two-editors race the
|
|
567
|
+
// surrounding flow already handles: the release push retries it,
|
|
568
|
+
// and if THAT fails the user gets the typed agent hand-off plus the
|
|
569
|
+
// banner from `pushWithRecovery`. Bannering it from a background
|
|
570
|
+
// autosave would tell an author mid-keystroke that the deployment
|
|
571
|
+
// is broken over a race that usually settles on its own — while an
|
|
572
|
+
// auth failure swallowed into a log line is how a broken deployment
|
|
573
|
+
// once stacked 136 local commits before anyone found out.
|
|
574
|
+
if (!looksLikeNonFastForward) {
|
|
575
|
+
this.noteGitSyncFailed(workspaceId, branch, recoveryError ?? err);
|
|
576
|
+
}
|
|
558
577
|
console.warn('[workflow] push after autosave commit failed (commit landed locally):', detail);
|
|
559
578
|
}
|
|
560
579
|
}
|
|
@@ -706,6 +725,81 @@ export class WorkflowService {
|
|
|
706
725
|
// converges on. Backend reactors (catalog invalidation, id-repair) hook here.
|
|
707
726
|
this.fileChanges?.emit({ workspaceId, branch, paths: [targetPath], byUser: user });
|
|
708
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Workspaces whose last push failed. Purely so the recovery event fires on
|
|
730
|
+
* the transition back to healthy instead of on every subsequent save — a
|
|
731
|
+
* push runs on every write, and the healthy case must stay silent.
|
|
732
|
+
*
|
|
733
|
+
* Process-local and deliberately not persisted: a restart clears it, and the
|
|
734
|
+
* worst that costs is one redundant `git-sync-recovered` on a workspace that
|
|
735
|
+
* was already fine, which clients treat as a no-op.
|
|
736
|
+
*/
|
|
737
|
+
gitSyncFailing = new Set();
|
|
738
|
+
/**
|
|
739
|
+
* Announce that a push landed. Emits only when this workspace was previously
|
|
740
|
+
* failing — see `gitSyncFailing`.
|
|
741
|
+
*/
|
|
742
|
+
noteGitSyncOk(workspaceId, branch) {
|
|
743
|
+
// Callers hand this both spellings of a workspace id — HTTP routes pass
|
|
744
|
+
// the Express-decoded `user/feat`, internal callers (the pending-commits
|
|
745
|
+
// worker, the agent filesystem) the encoded `user%2Ffeat`. Keyed raw, a
|
|
746
|
+
// failure recorded by one caller could never be cleared by the other's
|
|
747
|
+
// success, and the banner would stick. Canonicalize the key AND the
|
|
748
|
+
// emitted id so every consumer sees one spelling.
|
|
749
|
+
const id = branchForWorkspaceId(workspaceId);
|
|
750
|
+
if (!this.gitSyncFailing.delete(id))
|
|
751
|
+
return;
|
|
752
|
+
console.log(`[workflow] git sync recovered for workspace=${id} branch=${branch}`);
|
|
753
|
+
this.events?.emit({ kind: 'git-sync-recovered', workspaceId: id, branch });
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Announce that a commit is sitting locally because its push failed.
|
|
757
|
+
*
|
|
758
|
+
* This is the visibility half of the failure paths below; it never changes
|
|
759
|
+
* what they DO. The commit is already safe on disk and the retry / hand-off
|
|
760
|
+
* machinery is unaffected — all this adds is that someone finds out, rather
|
|
761
|
+
* than the deployment looking healthy while nothing reaches the remote.
|
|
762
|
+
*/
|
|
763
|
+
noteGitSyncFailed(workspaceId, branch, err) {
|
|
764
|
+
// Same canonicalization as `noteGitSyncOk` — the pair must agree on keys.
|
|
765
|
+
const id = branchForWorkspaceId(workspaceId);
|
|
766
|
+
this.gitSyncFailing.add(id);
|
|
767
|
+
this.events?.emit({
|
|
768
|
+
kind: 'git-sync-failed',
|
|
769
|
+
workspaceId: id,
|
|
770
|
+
branch,
|
|
771
|
+
// Git stderr can quote a credentialed URL; the banner is user-facing and
|
|
772
|
+
// the string also lands in client logs, so sanitize before it leaves.
|
|
773
|
+
reason: sanitizeError(err),
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* A bare push wrapped in the sync tracker: a failure raises the banner, a
|
|
778
|
+
* success clears it. For the workflow paths that push directly — sharing a
|
|
779
|
+
* branch, publishing a CR's source, update-from-base, the decline revert,
|
|
780
|
+
* the roles.yaml restore — rather than through `pushWithRecovery`'s
|
|
781
|
+
* cooperative ladder. Behaviour is otherwise unchanged: the error still
|
|
782
|
+
* propagates to the caller exactly as the bare push's did. Without this,
|
|
783
|
+
* those pushes could fail invisibly (no banner) and, worse, a successful
|
|
784
|
+
* share could not CLEAR a banner an earlier save had raised.
|
|
785
|
+
*
|
|
786
|
+
* The branch is derived from the workspace id (they are the same string,
|
|
787
|
+
* URL-encoding aside) so call sites cannot pass a mismatched pair.
|
|
788
|
+
*/
|
|
789
|
+
async trackedPush(workspaceId, user, opts) {
|
|
790
|
+
const branch = branchForWorkspaceId(workspaceId);
|
|
791
|
+
try {
|
|
792
|
+
// Preserve the exact call shape of the bare pushes this replaces — an
|
|
793
|
+
// explicit `undefined` third argument is a different call signature to
|
|
794
|
+
// every spy that asserts on it.
|
|
795
|
+
await (opts ? this.git.push(workspaceId, user, opts) : this.git.push(workspaceId, user));
|
|
796
|
+
this.noteGitSyncOk(workspaceId, branch);
|
|
797
|
+
}
|
|
798
|
+
catch (err) {
|
|
799
|
+
this.noteGitSyncFailed(workspaceId, branch, err);
|
|
800
|
+
throw err;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
709
803
|
/**
|
|
710
804
|
* Shared implementation of "push, and on non-fast-forward try a
|
|
711
805
|
* cooperative pull-rebase + retry, then hand off to the agent if that
|
|
@@ -715,25 +809,37 @@ export class WorkflowService {
|
|
|
715
809
|
async pushWithRecovery(workspaceId, branch, targetPath, user, opts) {
|
|
716
810
|
try {
|
|
717
811
|
await this.git.push(workspaceId, user, opts);
|
|
812
|
+
this.noteGitSyncOk(workspaceId, branch);
|
|
718
813
|
}
|
|
719
814
|
catch (firstPushErr) {
|
|
720
815
|
const firstDetail = firstPushErr instanceof Error ? firstPushErr.message : String(firstPushErr);
|
|
721
816
|
const looksLikeNonFastForward = /non-fast-forward|rejected|fetch first|updates were rejected/i.test(firstDetail);
|
|
722
817
|
let recovered = false;
|
|
723
818
|
let recoveryDetail = '(cooperative path not attempted)';
|
|
819
|
+
let recoveryError = null;
|
|
724
820
|
if (looksLikeNonFastForward) {
|
|
725
821
|
try {
|
|
726
822
|
await this.git.pull(workspaceId);
|
|
727
823
|
await this.git.push(workspaceId, user, opts);
|
|
728
824
|
recovered = true;
|
|
825
|
+
this.noteGitSyncOk(workspaceId, branch);
|
|
729
826
|
console.log(`[workflow] non-fast-forward push recovered via pull --rebase for workspace=${workspaceId} branch=${branch} path=${targetPath}`);
|
|
730
827
|
}
|
|
731
828
|
catch (recoveryErr) {
|
|
829
|
+
recoveryError = recoveryErr;
|
|
732
830
|
recoveryDetail = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
|
|
733
831
|
console.warn(`[workflow] cooperative recovery (pull-rebase) failed for workspace=${workspaceId} user=${user.id}; handing off to agent:`, recoveryDetail);
|
|
734
832
|
}
|
|
735
833
|
}
|
|
736
834
|
if (!recovered) {
|
|
835
|
+
// Banner first, then the typed throw. The throw only reaches a UI that
|
|
836
|
+
// is still mounted and listening — the release fired as an editor
|
|
837
|
+
// unmounts has nowhere to surface it — whereas the event reaches every
|
|
838
|
+
// session on the branch regardless of what triggered the push.
|
|
839
|
+
// When recovery RAN, its error is the current state of the world (the
|
|
840
|
+
// first rejection may be a stale non-fast-forward the pull already
|
|
841
|
+
// cured); when it was skipped, the first error is all there is.
|
|
842
|
+
this.noteGitSyncFailed(workspaceId, branch, recoveryError ?? firstPushErr);
|
|
737
843
|
console.warn(`[workflow] push failed for workspace=${workspaceId} user=${user.id}; throwing PushNeedsAgentResolutionError so the frontend can hand off to the agent:`, firstDetail);
|
|
738
844
|
throw new PushNeedsAgentResolutionError(branch, targetPath, firstDetail, recoveryDetail);
|
|
739
845
|
}
|
|
@@ -938,7 +1044,7 @@ export class WorkflowService {
|
|
|
938
1044
|
// Push the source — `gh pr create` against an unpushed branch returns
|
|
939
1045
|
// "head branch does not exist on remote". Push *after* the auto-merge
|
|
940
1046
|
// so the remote sees the merge commit too.
|
|
941
|
-
await this.
|
|
1047
|
+
await this.trackedPush(workspaceId, user);
|
|
942
1048
|
const workspacePath = await this.workspaceService.getWorkspacePath(workspaceId);
|
|
943
1049
|
const cwd = path.join(workspacePath, this.kbDirName);
|
|
944
1050
|
// Touched paths between target and source — basis for the
|
|
@@ -1045,7 +1151,7 @@ export class WorkflowService {
|
|
|
1045
1151
|
// If a new merge commit landed, push so the CR picks it up. When
|
|
1046
1152
|
// already up to date there's nothing to share — short-circuit the push.
|
|
1047
1153
|
if (!outcome.alreadyUpToDate) {
|
|
1048
|
-
await this.
|
|
1154
|
+
await this.trackedPush(workspaceId, user);
|
|
1049
1155
|
}
|
|
1050
1156
|
this.prs.invalidateDetailCache(number);
|
|
1051
1157
|
const refreshed = await this.prs.getPrDetail(number, {
|
|
@@ -1154,7 +1260,7 @@ export class WorkflowService {
|
|
|
1154
1260
|
try {
|
|
1155
1261
|
await this.git.restorePathFromRef(ws.id, mergeBase, repoRelPath);
|
|
1156
1262
|
await this.git.commitFile(ws.id, user, repoRelPath, `Revert ${repoRelPath} (declined in change request #${number})`, true);
|
|
1157
|
-
await this.
|
|
1263
|
+
await this.trackedPush(ws.id, user);
|
|
1158
1264
|
}
|
|
1159
1265
|
finally {
|
|
1160
1266
|
// Committed inline — drop the lock row directly rather than enqueueing
|
|
@@ -1615,7 +1721,7 @@ export class WorkflowService {
|
|
|
1615
1721
|
if (!committed) {
|
|
1616
1722
|
throw new Error('roles.yaml restore produced no commit while origin still diverges from base — source workspace out of sync; refusing to merge');
|
|
1617
1723
|
}
|
|
1618
|
-
await this.
|
|
1724
|
+
await this.trackedPush(ws.id, user);
|
|
1619
1725
|
this.accessControl.invalidate(ws.id);
|
|
1620
1726
|
return true;
|
|
1621
1727
|
}
|