* Resolve the active node_secret with conflict reconciliation between the * persistent MailboxStore and `process.env.A2A_NODE_SECRET`. * * Two opposite failure modes shape this logic: * * #529 (env-fresh, store-stale): operator exports a freshly minted * secret in A2A_NODE_SE
()
| 568 | * @returns {string|null} |
| 569 | */ |
| 570 | _resolveNodeSecret() { |
| 571 | const envSecret = this._effectiveEnvNodeSecret(); |
| 572 | const envUpdatedAfterSuppression = this._envSuppressionClearedForSecret === envSecret; |
| 573 | const storeSecret = this.store.getState('node_secret') || null; |
| 574 | const storeSource = this.store.getState('node_secret_source') || null; |
| 575 | |
| 576 | if ( |
| 577 | envSecret && |
| 578 | envUpdatedAfterSuppression && |
| 579 | validNodeSecret(envSecret) && |
| 580 | (!storeSecret || envSecret === storeSecret) && |
| 581 | !(storeSecret && envSecret === storeSecret && storeSource === 'hub_rotate') |
| 582 | ) { |
| 583 | this._syncEnvNodeSecretToStore(envSecret); |
| 584 | return envSecret; |
| 585 | } |
| 586 | |
| 587 | if (envSecret && storeSecret && envSecret !== storeSecret) { |
| 588 | // Store value came from a successful hub rotation -> trust it. |
| 589 | // The env var is necessarily stale: it was captured by the parent |
| 590 | // shell before the rotation and a child process cannot mutate it |
| 591 | // back into its parent. |
| 592 | if (storeSource === 'hub_rotate' && validNodeSecret(storeSecret) && !envUpdatedAfterSuppression) { |
| 593 | if (!this._storeSourceLogged) { |
| 594 | this._storeSourceLogged = true; |
| 595 | this.logger.warn( |
| 596 | '[lifecycle] A2A_NODE_SECRET env var differs from MailboxStore; ' + |
| 597 | 'store value originated from a hub rotation, treating env as stale. ' + |
| 598 | 'After a manual web reset, run `evolver reset-local-secret` to clear local ' + |
| 599 | 'secret and env suppression state before relying only on updated env vars.' |
| 600 | ); |
| 601 | } |
| 602 | return storeSecret; |
| 603 | } |
| 604 | if (validNodeSecret(envSecret)) { |
| 605 | const diskRotated = this._latestDiskHubRotatedSecret(); |
| 606 | if (diskRotated) { |
| 607 | this._setNodeSecretState({ |
| 608 | secret: diskRotated.secret, |
| 609 | version: diskRotated.version ? String(diskRotated.version) : '', |
| 610 | source: 'hub_rotate', |
| 611 | envSuppressed: this.store.getState('node_secret_env_suppressed') || '', |
| 612 | }); |
| 613 | if (!this._storeSourceLogged) { |
| 614 | this._storeSourceLogged = true; |
| 615 | this.logger.warn( |
| 616 | '[lifecycle] MailboxStore memory differs from disk hub-rotated node_secret; ' + |
| 617 | 'using disk value and treating env as stale.' |
| 618 | ); |
| 619 | } |
| 620 | return diskRotated.secret; |
| 621 | } |
| 622 | this._syncEnvNodeSecretToStore(envSecret); |
| 623 | if (!this._envOverrideLogged) { |
| 624 | this._envOverrideLogged = true; |
| 625 | this.logger.warn( |
| 626 | '[lifecycle] A2A_NODE_SECRET env var differs from MailboxStore; using env value and syncing store. ' + |
| 627 | 'Clear ~/.evomap/mailbox/state.json or unset A2A_NODE_SECRET to silence this warning.' |
no test coverage detected