@dtlabs/ai-toolkit 0.12.0 → 0.14.0
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 +72 -0
- package/bin/cli.js +851 -9
- package/docs/task-executor-bootstrap.md +195 -0
- package/docs/task-executor-cli.md +319 -0
- package/docs/workflow-control-plane.md +95 -0
- package/lib/agent-registry.js +1018 -0
- package/lib/execution-ledger.js +283 -7
- package/lib/task-executor/claude-process.js +563 -0
- package/lib/task-executor/git.js +1362 -0
- package/lib/task-executor/index.js +3729 -0
- package/lib/task-executor/ownership.js +590 -0
- package/lib/task-executor/plan.js +725 -0
- package/lib/task-executor/store.js +1164 -0
- package/lib/workflow-artifacts.js +102 -0
- package/lib/workflow-control.js +219 -0
- package/lib/workflow-runner.js +39 -0
- package/package.json +1 -1
- package/src/claude/agents/{concurrency-safety-assessment.md → gaia-concurrency-safety-assessment.md} +1 -1
- package/src/claude/agents/{define-feature.md → gaia-define-feature.md} +1 -1
- package/src/claude/agents/{dependency-injection-refactoring.md → gaia-dependency-injection-refactoring.md} +1 -1
- package/src/claude/agents/{dependency-supply-chain-security.md → gaia-dependency-supply-chain-security.md} +1 -1
- package/src/claude/agents/{developer-backend.md → gaia-developer-backend.md} +1 -1
- package/src/claude/agents/{developer-frontend.md → gaia-developer-frontend.md} +1 -1
- package/src/claude/agents/{developer-testing.md → gaia-developer-testing.md} +1 -1
- package/src/claude/agents/{domain-model-refactoring.md → gaia-domain-model-refactoring.md} +1 -1
- package/src/claude/agents/{generate-requirements.md → gaia-generate-requirements.md} +1 -1
- package/src/claude/agents/{generate-tech-spec.md → gaia-generate-tech-spec.md} +1 -1
- package/src/claude/agents/{generate-work-breakdown.md → gaia-generate-work-breakdown.md} +5 -5
- package/src/claude/agents/{generic-software-assessment.md → gaia-generic-software-assessment.md} +1 -1
- package/src/claude/agents/{god-class-decomposition.md → gaia-god-class-decomposition.md} +1 -1
- package/src/claude/agents/{init-agents-md.md → gaia-init-agents-md.md} +1 -1
- package/src/claude/agents/{install-toolkit.md → gaia-install-toolkit.md} +1 -1
- package/src/claude/agents/{intervention-documentation-standard.md → gaia-intervention-documentation-standard.md} +10 -10
- package/src/claude/agents/{layered-architecture-assessment.md → gaia-layered-architecture-assessment.md} +1 -1
- package/src/claude/agents/{review-solution.md → gaia-review-solution.md} +1 -1
- package/src/claude/agents/{security-hardening.md → gaia-security-hardening.md} +1 -1
- package/src/claude/agents/{validate-feature-docs.md → gaia-validate-feature-docs.md} +2 -2
- package/src/claude/agents/{validate-work-breakdown-semantic.md → gaia-validate-work-breakdown-semantic.md} +1 -1
- package/src/claude/commands/next-task.md +4 -4
- package/src/claude/skills/assess-codebase/SKILL.md +9 -103
- package/src/claude/skills/define-feature/SKILL.md +2 -2
- package/src/claude/skills/hi-gaia/SKILL.md +1 -1
- package/src/claude/skills/implement-feature/SKILL.md +131 -38
- package/src/claude/skills/init-agents/SKILL.md +2 -2
- package/src/claude/skills/install-toolkit/SKILL.md +2 -2
- package/src/claude/workflows/am-phase1.js +43 -294
- package/src/claude/workflows/am-phase2.js +10 -164
- package/src/claude/workflows/pm-phase1.js +45 -287
- package/src/claude/workflows/pm-phase2.js +51 -511
- package/src/claude/workflows/pm-phase3.js +5 -767
package/lib/execution-ledger.js
CHANGED
|
@@ -33,7 +33,8 @@ function _readLedger(ledgerPath) {
|
|
|
33
33
|
}
|
|
34
34
|
throw parseErr;
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
if (!Array.isArray(parsed)) throw new Error('Invalid ledger: expected JSON array');
|
|
37
|
+
return { entries: parsed };
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
function _writeLedger(ledgerPath, entries) {
|
|
@@ -244,6 +245,73 @@ function _isLockStale(content, stat) {
|
|
|
244
245
|
return mtimeAge > LOCK_ORPHAN_THRESHOLD_MS ? 'reclaimable' : 'wait';
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
// ── Metadata whitelist and reserved-field protection (INFRA-TASK-BE-03) ─────
|
|
249
|
+
|
|
250
|
+
const METADATA_WHITELIST = new Set([
|
|
251
|
+
'agentId',
|
|
252
|
+
'nativeAgentName',
|
|
253
|
+
'platform',
|
|
254
|
+
'toolkitVersion',
|
|
255
|
+
'resolutionScope',
|
|
256
|
+
'definitionHash',
|
|
257
|
+
]);
|
|
258
|
+
|
|
259
|
+
const RESERVED_FIELDS = new Set([
|
|
260
|
+
'operation_id',
|
|
261
|
+
'agent',
|
|
262
|
+
'phase',
|
|
263
|
+
'model',
|
|
264
|
+
'status',
|
|
265
|
+
'started_at',
|
|
266
|
+
'completed_at',
|
|
267
|
+
'phase_delta_tokens',
|
|
268
|
+
'error',
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
function _validateMetadata(metadata) {
|
|
272
|
+
for (const key of Object.keys(metadata)) {
|
|
273
|
+
if (RESERVED_FIELDS.has(key)) {
|
|
274
|
+
const err = new Error(
|
|
275
|
+
'open: metadata key "' + key + '" is a reserved field and cannot be set via metadata'
|
|
276
|
+
);
|
|
277
|
+
err.code = 'METADATA_RESERVED_FIELD';
|
|
278
|
+
err.key = key;
|
|
279
|
+
throw err;
|
|
280
|
+
}
|
|
281
|
+
if (!METADATA_WHITELIST.has(key)) {
|
|
282
|
+
const err = new Error(
|
|
283
|
+
'open: metadata key "' + key + '" is not in the whitelist; ' +
|
|
284
|
+
'allowed keys: ' + Array.from(METADATA_WHITELIST).join(', ')
|
|
285
|
+
);
|
|
286
|
+
err.code = 'METADATA_UNKNOWN_KEY';
|
|
287
|
+
err.key = key;
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function _extractStoredMetadata(entry) {
|
|
294
|
+
const result = {};
|
|
295
|
+
for (const key of METADATA_WHITELIST) {
|
|
296
|
+
if (Object.prototype.hasOwnProperty.call(entry, key)) {
|
|
297
|
+
result[key] = entry[key];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function _metadataEqual(storedEntry, newMetadata) {
|
|
304
|
+
const stored = _extractStoredMetadata(storedEntry);
|
|
305
|
+
const storedKeys = Object.keys(stored).sort();
|
|
306
|
+
const newKeys = Object.keys(newMetadata).sort();
|
|
307
|
+
|
|
308
|
+
if (storedKeys.length !== newKeys.length) return false;
|
|
309
|
+
for (var i = 0; i < newKeys.length; i++) {
|
|
310
|
+
if (stored[newKeys[i]] !== newMetadata[newKeys[i]]) return false;
|
|
311
|
+
}
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
|
|
247
315
|
// ── Public API (stubs — implemented by INFRA-TASK-BE-02 and US-0x tasks) ────
|
|
248
316
|
|
|
249
317
|
function computeOperationId(prefix, agent, attempt) {
|
|
@@ -251,9 +319,14 @@ function computeOperationId(prefix, agent, attempt) {
|
|
|
251
319
|
return crypto.createHash('sha256').update(input, 'utf8').digest('hex').slice(0, 32);
|
|
252
320
|
}
|
|
253
321
|
|
|
254
|
-
function open(dir, prefix, agent, phase, model, attempt) {
|
|
322
|
+
function open(dir, prefix, agent, phase, model, attempt, metadata) {
|
|
255
323
|
if (attempt == null) attempt = 1;
|
|
256
324
|
|
|
325
|
+
// Validate metadata keys BEFORE any lock or I/O (fail-closed).
|
|
326
|
+
if (metadata != null) {
|
|
327
|
+
_validateMetadata(metadata);
|
|
328
|
+
}
|
|
329
|
+
|
|
257
330
|
const ledgerPath = path.join(dir, prefix + '-token-ledger.json');
|
|
258
331
|
const lockPath = ledgerPath + '.lock';
|
|
259
332
|
|
|
@@ -268,11 +341,25 @@ function open(dir, prefix, agent, phase, model, attempt) {
|
|
|
268
341
|
});
|
|
269
342
|
|
|
270
343
|
if (existingIndex !== -1) {
|
|
344
|
+
_assertUnowned(entries[existingIndex]);
|
|
345
|
+
// Idempotent re-open: check metadata conflict BEFORE writing anything.
|
|
346
|
+
if (metadata != null) {
|
|
347
|
+
if (!_metadataEqual(entries[existingIndex], metadata)) {
|
|
348
|
+
const err = new Error(
|
|
349
|
+
'open: metadata conflict for operation_id ' + operation_id +
|
|
350
|
+
' — supplied metadata differs from stored metadata; re-open rejected (fail-closed)'
|
|
351
|
+
);
|
|
352
|
+
err.code = 'METADATA_CONFLICT';
|
|
353
|
+
err.operation_id = operation_id;
|
|
354
|
+
throw err;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
271
357
|
// Resume: idempotently preserve original started_at and any positive
|
|
272
358
|
// phase_delta_tokens — only reset status to running.
|
|
273
359
|
entries[existingIndex] = Object.assign({}, entries[existingIndex], { status: 'running' });
|
|
274
360
|
} else {
|
|
275
|
-
|
|
361
|
+
// First open: store standard fields, then spread validated metadata fields.
|
|
362
|
+
const newEntry = {
|
|
276
363
|
operation_id: operation_id,
|
|
277
364
|
agent: agent,
|
|
278
365
|
phase: phase,
|
|
@@ -281,7 +368,11 @@ function open(dir, prefix, agent, phase, model, attempt) {
|
|
|
281
368
|
started_at: started_at,
|
|
282
369
|
completed_at: null,
|
|
283
370
|
phase_delta_tokens: null,
|
|
284
|
-
}
|
|
371
|
+
};
|
|
372
|
+
if (metadata != null) {
|
|
373
|
+
Object.assign(newEntry, metadata);
|
|
374
|
+
}
|
|
375
|
+
entries.push(newEntry);
|
|
285
376
|
}
|
|
286
377
|
|
|
287
378
|
_writeLedger(ledgerPath, entries);
|
|
@@ -333,7 +424,8 @@ function close(dir, prefix, agent, tokens, attempt) {
|
|
|
333
424
|
idx = agentIndexes[0];
|
|
334
425
|
}
|
|
335
426
|
|
|
336
|
-
const existing
|
|
427
|
+
const existing = entries[idx];
|
|
428
|
+
_assertUnowned(existing);
|
|
337
429
|
const completed_at = new Date().toISOString();
|
|
338
430
|
|
|
339
431
|
// Token preservation (AC-10):
|
|
@@ -407,7 +499,8 @@ function fail(dir, prefix, agent, error, attempt) {
|
|
|
407
499
|
idx = agentIndexes[0];
|
|
408
500
|
}
|
|
409
501
|
|
|
410
|
-
const existing
|
|
502
|
+
const existing = entries[idx];
|
|
503
|
+
_assertUnowned(existing);
|
|
411
504
|
const completed_at = new Date().toISOString();
|
|
412
505
|
|
|
413
506
|
const update = {
|
|
@@ -483,7 +576,8 @@ function skip(dir, prefix, agent, phase, model, attempt) {
|
|
|
483
576
|
}
|
|
484
577
|
|
|
485
578
|
// Case 2: EXACTLY ONE match — update in place, preserving started_at and all other fields.
|
|
486
|
-
const existing
|
|
579
|
+
const existing = entries[idx];
|
|
580
|
+
_assertUnowned(existing);
|
|
487
581
|
const completed_at = new Date().toISOString();
|
|
488
582
|
|
|
489
583
|
entries[idx] = Object.assign({}, existing, {
|
|
@@ -500,11 +594,188 @@ function skip(dir, prefix, agent, phase, model, attempt) {
|
|
|
500
594
|
}
|
|
501
595
|
}
|
|
502
596
|
|
|
597
|
+
// ── finalizeActivity (INFRA-TASK-BE-02) ──────────────────────────────────────
|
|
598
|
+
//
|
|
599
|
+
// Additive terminal-state API used by the executor. Unlike close()/fail()/skip(),
|
|
600
|
+
// it requires an exact operation_id match (no name-only agent fallback), can
|
|
601
|
+
// record tokens on ANY terminal status (including 'failed'), accepts an optional
|
|
602
|
+
// usage_reason (additive ledger field, NOT an open() metadata whitelist key),
|
|
603
|
+
// and is replay-safe: finalizing an already-terminal entry with identical data
|
|
604
|
+
// is a no-op that returns the existing entry unchanged, while conflicting data
|
|
605
|
+
// fails closed without writing. It does not introduce a second ledger file or
|
|
606
|
+
// a duplicate token counter — it reuses phase_delta_tokens like close()/fail().
|
|
607
|
+
|
|
608
|
+
const FINALIZE_STATUSES = new Set(['done', 'failed', 'skipped']);
|
|
609
|
+
const TERMINAL_STATUSES = new Set(['done', 'failed', 'skipped']);
|
|
610
|
+
|
|
611
|
+
function finalizeActivity(dir, prefix, agent, attempt, options) {
|
|
612
|
+
if (attempt == null) attempt = 1;
|
|
613
|
+
options = options || {};
|
|
614
|
+
|
|
615
|
+
const status = options.status;
|
|
616
|
+
const tokens = (options.tokens === undefined) ? null : options.tokens;
|
|
617
|
+
const reason = options.reason;
|
|
618
|
+
const completedAtArg = options.completedAt;
|
|
619
|
+
|
|
620
|
+
if (!FINALIZE_STATUSES.has(status)) {
|
|
621
|
+
const err = new Error(
|
|
622
|
+
'finalizeActivity: status must be one of "done", "failed", "skipped"; got ' +
|
|
623
|
+
JSON.stringify(status)
|
|
624
|
+
);
|
|
625
|
+
err.code = 'INVALID_STATUS';
|
|
626
|
+
throw err;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const ledgerPath = path.join(dir, prefix + '-token-ledger.json');
|
|
630
|
+
const lockPath = ledgerPath + '.lock';
|
|
631
|
+
|
|
632
|
+
const lockHandle = _acquireLock(lockPath);
|
|
633
|
+
try {
|
|
634
|
+
const { entries } = _readLedger(ledgerPath);
|
|
635
|
+
const operation_id = computeOperationId(prefix, agent, attempt);
|
|
636
|
+
|
|
637
|
+
// Exact-ID lookup only — no name-only agent fallback (unlike close/fail/skip).
|
|
638
|
+
const idx = entries.findIndex(function (e) {
|
|
639
|
+
return e.operation_id === operation_id;
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
if (idx === -1) {
|
|
643
|
+
const err = new Error(
|
|
644
|
+
'finalizeActivity: no entry found for operation_id ' + operation_id +
|
|
645
|
+
' in ' + ledgerPath + ' — exact operation_id match required, ' +
|
|
646
|
+
'no name-only fallback; operation was never opened'
|
|
647
|
+
);
|
|
648
|
+
err.code = 'ACTIVITY_NOT_FOUND';
|
|
649
|
+
err.operation_id = operation_id;
|
|
650
|
+
throw err;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const existing = entries[idx];
|
|
654
|
+
_assertUnowned(existing);
|
|
655
|
+
|
|
656
|
+
const suppliedTokens = (tokens === undefined) ? null : tokens;
|
|
657
|
+
const suppliedReason = (reason === undefined) ? null : reason;
|
|
658
|
+
|
|
659
|
+
if (TERMINAL_STATUSES.has(existing.status)) {
|
|
660
|
+
// Already finalized: this is either an idempotent replay of identical
|
|
661
|
+
// data (return unchanged, no write) or conflicting data (fail closed).
|
|
662
|
+
const existingTokens = (existing.phase_delta_tokens === undefined)
|
|
663
|
+
? null : existing.phase_delta_tokens;
|
|
664
|
+
const existingReason = (existing.usage_reason === undefined)
|
|
665
|
+
? null : existing.usage_reason;
|
|
666
|
+
const effectiveCompletedAt = (completedAtArg != null)
|
|
667
|
+
? completedAtArg : existing.completed_at;
|
|
668
|
+
|
|
669
|
+
const isIdentical =
|
|
670
|
+
existing.status === status &&
|
|
671
|
+
existingTokens === suppliedTokens &&
|
|
672
|
+
existingReason === suppliedReason &&
|
|
673
|
+
existing.completed_at === effectiveCompletedAt;
|
|
674
|
+
|
|
675
|
+
if (isIdentical) {
|
|
676
|
+
return { status: 'ok', operation_id: operation_id, entry: existing, replay: true };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const err = new Error(
|
|
680
|
+
'finalizeActivity: conflicting terminal data for operation_id ' + operation_id +
|
|
681
|
+
' in ' + ledgerPath + ' — existing terminal entry differs from the supplied ' +
|
|
682
|
+
'status/tokens/reason/completedAt; refusing to overwrite (fail-closed)'
|
|
683
|
+
);
|
|
684
|
+
err.code = 'TERMINAL_CONFLICT';
|
|
685
|
+
err.operation_id = operation_id;
|
|
686
|
+
throw err;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Not yet terminal: finalize it. Tokens are recorded even when status is
|
|
690
|
+
// 'failed' — first finalization records known tokens, or null plus an
|
|
691
|
+
// optional usage_reason explaining why usage is unavailable. Matching the
|
|
692
|
+
// close() token-preservation invariant: a null/undefined supplied value
|
|
693
|
+
// never clobbers an already-stored positive phase_delta_tokens.
|
|
694
|
+
const completed_at = (completedAtArg != null) ? completedAtArg : new Date().toISOString();
|
|
695
|
+
|
|
696
|
+
let phase_delta_tokens = existing.phase_delta_tokens;
|
|
697
|
+
if (suppliedTokens !== null) {
|
|
698
|
+
phase_delta_tokens = suppliedTokens;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const update = {
|
|
702
|
+
status: status,
|
|
703
|
+
completed_at: completed_at,
|
|
704
|
+
phase_delta_tokens: phase_delta_tokens,
|
|
705
|
+
};
|
|
706
|
+
if (reason !== undefined) {
|
|
707
|
+
update.usage_reason = reason;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Merge update: Object.assign preserves unknown/legacy fields and metadata verbatim.
|
|
711
|
+
entries[idx] = Object.assign({}, existing, update);
|
|
712
|
+
|
|
713
|
+
_writeLedger(ledgerPath, entries);
|
|
714
|
+
|
|
715
|
+
return { status: 'ok', operation_id: operation_id, entry: entries[idx] };
|
|
716
|
+
} finally {
|
|
717
|
+
_releaseLock(lockPath, lockHandle);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Workflow-owned entries cannot be finalized by worker/legacy CLI calls.
|
|
722
|
+
function _assertUnowned(entry) {
|
|
723
|
+
if (entry && entry.workflow_owner) {
|
|
724
|
+
const error = new Error('Workflow entry requires its owner capability');
|
|
725
|
+
error.code = 'LEDGER_OWNER_REQUIRED';
|
|
726
|
+
throw error;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function openOwned(dir, prefix, agent, phase, model, owner, metadata) {
|
|
731
|
+
if (typeof owner !== 'string' || !owner) throw new Error('Workflow owner required');
|
|
732
|
+
if (metadata) _validateMetadata(metadata);
|
|
733
|
+
const ledgerPath = path.join(dir, prefix + '-token-ledger.json');
|
|
734
|
+
const lockPath = ledgerPath + '.lock';
|
|
735
|
+
const handle = _acquireLock(lockPath);
|
|
736
|
+
try {
|
|
737
|
+
const { entries } = _readLedger(ledgerPath);
|
|
738
|
+
const operation_id = computeOperationId(prefix, agent, 1);
|
|
739
|
+
if (entries.some(e => e.operation_id === operation_id)) throw new Error('Owned operation already opened');
|
|
740
|
+
const entry = { operation_id, agent, phase, model, workflow_owner: owner,
|
|
741
|
+
status: 'running', started_at: new Date().toISOString(), completed_at: null, phase_delta_tokens: null };
|
|
742
|
+
if (metadata) Object.assign(entry, metadata);
|
|
743
|
+
entries.push(entry);
|
|
744
|
+
_writeLedger(ledgerPath, entries);
|
|
745
|
+
return entry;
|
|
746
|
+
} finally { _releaseLock(lockPath, handle); }
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function finalizeOwned(dir, prefix, agent, owner, status, error, tokens) {
|
|
750
|
+
if (!['done', 'failed', 'skipped'].includes(status)) throw new Error('Invalid owned terminal status');
|
|
751
|
+
const ledgerPath = path.join(dir, prefix + '-token-ledger.json');
|
|
752
|
+
const lockPath = ledgerPath + '.lock';
|
|
753
|
+
const handle = _acquireLock(lockPath);
|
|
754
|
+
try {
|
|
755
|
+
const { entries } = _readLedger(ledgerPath);
|
|
756
|
+
const id = computeOperationId(prefix, agent, 1);
|
|
757
|
+
const entry = entries.find(e => e.operation_id === id);
|
|
758
|
+
if (!entry || !owner || entry.workflow_owner !== owner) throw new Error('Ledger ownership mismatch');
|
|
759
|
+
if (entry.status !== 'running') throw new Error('Owned terminal transition conflict');
|
|
760
|
+
if (tokens != null && (!Number.isInteger(tokens) || tokens <= 0)) throw new Error('Invalid owned token measurement');
|
|
761
|
+
if (tokens != null) entry.phase_delta_tokens = tokens;
|
|
762
|
+
entry.status = status;
|
|
763
|
+
entry.completed_at = new Date().toISOString();
|
|
764
|
+
if (error) entry.error = _sanitizeError(error);
|
|
765
|
+
_writeLedger(ledgerPath, entries);
|
|
766
|
+
return entry;
|
|
767
|
+
} finally { _releaseLock(lockPath, handle); }
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
|
|
503
771
|
module.exports = {
|
|
772
|
+
openOwned,
|
|
773
|
+
finalizeOwned,
|
|
504
774
|
open,
|
|
505
775
|
close,
|
|
506
776
|
fail,
|
|
507
777
|
skip,
|
|
778
|
+
finalizeActivity,
|
|
508
779
|
computeOperationId,
|
|
509
780
|
_readLedger,
|
|
510
781
|
_writeLedger,
|
|
@@ -512,4 +783,9 @@ module.exports = {
|
|
|
512
783
|
_acquireLock,
|
|
513
784
|
_releaseLock,
|
|
514
785
|
_isLockStale,
|
|
786
|
+
_validateMetadata,
|
|
787
|
+
_extractStoredMetadata,
|
|
788
|
+
_metadataEqual,
|
|
789
|
+
METADATA_WHITELIST,
|
|
790
|
+
RESERVED_FIELDS,
|
|
515
791
|
};
|