gowalk-cicd 1.0.124 → 1.0.125
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/action/.daemux-version +1 -1
- package/android-action/.daemux-version +1 -1
- package/android-action/play-upload/owner.cjs +38 -5
- package/android-action/play-upload/owner_http.cjs +4 -0
- package/android-action/play-upload/owner_run.cjs +2 -1
- package/android-action/play-upload/run.cjs +2 -1
- package/backend-action/.daemux-version +1 -1
- package/package.json +1 -1
package/action/.daemux-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.125
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.125
|
|
@@ -3,17 +3,31 @@ const record = require('./owner_record.cjs');
|
|
|
3
3
|
const {performance} = require('node:perf_hooks');
|
|
4
4
|
const {setTimeout: delay} = require('node:timers/promises');
|
|
5
5
|
|
|
6
|
+
// The last ownership write's outcome, as one allow-listed token. A release that
|
|
7
|
+
// cannot be verified used to name nothing at all, so a 403 (token identity), a
|
|
8
|
+
// 409 (CAS race) and a silent transport fault were one indistinguishable code
|
|
9
|
+
// and no session could act on it (Foundy, 2026-09-09, run 34345638488).
|
|
10
|
+
function evidence(status) {
|
|
11
|
+
if (Number.isInteger(status) && status >= 100 && status <= 599) return `release_write_status=${status}`;
|
|
12
|
+
return 'release_write_status=error';
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
async function seal(client, selected) {
|
|
16
|
+
let detail = 'release_write_status=none';
|
|
7
17
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
8
18
|
const row = record.validate(await client.read(selected.path), selected);
|
|
9
19
|
if (row.value.owner !== null && !record.same(row.value.owner, selected.owner)) return;
|
|
10
20
|
const idle = record.replacement(row, null);
|
|
11
|
-
|
|
21
|
+
// Reconcile a possibly applied write; keep only its status as evidence.
|
|
22
|
+
try { detail = evidence(await client.write(selected.path, row, idle)); }
|
|
23
|
+
catch { detail = evidence(undefined); }
|
|
12
24
|
const after = record.validate(await client.read(selected.path), selected);
|
|
13
25
|
if (after.value.revision === idle.revision && after.value.owner === null) return;
|
|
14
26
|
if (after.value.owner !== null && !record.same(after.value.owner, selected.owner)) return;
|
|
15
27
|
}
|
|
16
|
-
|
|
28
|
+
const failure = new Error('play_owner_release_unverified');
|
|
29
|
+
failure.detail = detail;
|
|
30
|
+
throw failure;
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
async function acquire(client, selected, attempted = () => {}, waiting = {}) {
|
|
@@ -59,7 +73,22 @@ async function withOwner(env, packageName, operation, transport, timing = {}) {
|
|
|
59
73
|
result = await operation(() => cancelled);
|
|
60
74
|
} catch (error) { failure = error; }
|
|
61
75
|
finally {
|
|
62
|
-
|
|
76
|
+
// An unverified release must not erase why the operation itself failed: the
|
|
77
|
+
// build's real error is the actionable one, and overwriting it left a
|
|
78
|
+
// session with a fence code and no cause. Keep the first failure and carry
|
|
79
|
+
// the release problem alongside it.
|
|
80
|
+
try { if (dirty) await seal(client, selected); }
|
|
81
|
+
catch (error) {
|
|
82
|
+
// The classified code is always what leaves this function: a raw read or
|
|
83
|
+
// transport error carries provider text that must never reach a receipt
|
|
84
|
+
// or an annotation. Only this file's own allow-listed token travels.
|
|
85
|
+
const detail = typeof error?.detail === 'string' ? error.detail : undefined;
|
|
86
|
+
if (failure) { if (detail) failure.releaseUnverified = detail; }
|
|
87
|
+
else {
|
|
88
|
+
failure = new Error('play_owner_release_unverified');
|
|
89
|
+
if (detail) failure.detail = detail;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
63
92
|
process.off('SIGTERM', stop); process.off('SIGINT', stop);
|
|
64
93
|
}
|
|
65
94
|
if (cancelled && (!failure || failure.name === 'AbortError')) failure = new Error('play_owner_cancelled');
|
|
@@ -72,10 +101,14 @@ async function withOwner(env, packageName, operation, transport, timing = {}) {
|
|
|
72
101
|
|
|
73
102
|
const codes = ['play_owner_binding_required', 'play_owner_unverified', 'play_owner_busy',
|
|
74
103
|
'play_owner_release_unverified', 'play_owner_cancelled', 'play_owner_principal_unverified'];
|
|
75
|
-
function annotation(reason, childExit) {
|
|
104
|
+
function annotation(reason, childExit, detail) {
|
|
76
105
|
const allowed = [...codes, 'play_owner_child_failed', 'play_owner_package_unverified'];
|
|
77
106
|
const code = allowed.includes(reason) ? reason : 'play_owner_unverified';
|
|
78
107
|
const exit = Number.isInteger(childExit) && childExit >= 0 && childExit <= 255 ? childExit : 'unknown';
|
|
79
|
-
|
|
108
|
+
// An optional trailing token, admitted only in the shape this file mints, so
|
|
109
|
+
// a provider message can never reach the annotation through it.
|
|
110
|
+
const extra = typeof detail === 'string' && /^release_write_status=(?:[1-5][0-9]{2}|error|none)$/.test(detail)
|
|
111
|
+
? ` ${detail}` : '';
|
|
112
|
+
return `::error title=play_edit_owner::gowalk-cicd/play-owner.v1 code=${code} child_exit_code=${exit}${extra}`;
|
|
80
113
|
}
|
|
81
114
|
module.exports = {acquire, seal, withOwner, codes, annotation};
|
|
@@ -55,6 +55,10 @@ function client(repo, token, transport = request) {
|
|
|
55
55
|
const response = await send('PUT', `/contents/${path}`, {branch, sha: old.sha,
|
|
56
56
|
message: 'Update scoped Play edit ownership', content: Buffer.from(JSON.stringify(value)).toString('base64')});
|
|
57
57
|
if (![200, 201, 409, 422].includes(response.status)) throw new Error('play_owner_unverified');
|
|
58
|
+
// The status is the only safe evidence a failed ownership write leaves:
|
|
59
|
+
// 409/422 is a CAS race, 403 a token identity. Callers reconcile through
|
|
60
|
+
// a readback and would otherwise discard it (`owner.cjs`).
|
|
61
|
+
return response.status;
|
|
58
62
|
},
|
|
59
63
|
run: owner => send('GET', `/actions/runs/${owner.run_id}/attempts/${owner.run_attempt}`),
|
|
60
64
|
};
|
|
@@ -51,7 +51,8 @@ async function main(argv = process.argv.slice(2), env = process.env) {
|
|
|
51
51
|
} catch (error) {
|
|
52
52
|
const code = [...owner.codes, 'play_owner_child_failed', 'play_owner_package_unverified'].includes(error.message)
|
|
53
53
|
? error.message : 'play_owner_unverified';
|
|
54
|
-
console.log(owner.annotation(code, error.operationResult?.code
|
|
54
|
+
console.log(owner.annotation(code, error.operationResult?.code,
|
|
55
|
+
error.detail || (typeof error.releaseUnverified === 'string' ? error.releaseUnverified : undefined)));
|
|
55
56
|
process.exitCode = 1;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -44,7 +44,8 @@ async function main() {
|
|
|
44
44
|
upload(program, env, directory, processRunner.execute, 3600000, cancelled,
|
|
45
45
|
(attempt, result) => console.log(diagnostics.annotation(attempt, result))));
|
|
46
46
|
} catch (error) {
|
|
47
|
-
if (owner.codes.includes(error.message)) console.log(owner.annotation(error.message
|
|
47
|
+
if (owner.codes.includes(error.message)) console.log(owner.annotation(error.message, undefined,
|
|
48
|
+
error.detail || (typeof error.releaseUnverified === 'string' ? error.releaseUnverified : undefined)));
|
|
48
49
|
const allowed = ['assigned_proxy_required', 'runner_context_required', 'uploader_integrity_refused',
|
|
49
50
|
'uploader_source_unavailable', ...owner.codes];
|
|
50
51
|
receipt = {ok: false, code: allowed.includes(error.message) ? error.message : 'uploader_failed',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.125
|