@evomap/evolver-adapter-public 2.0.0-beta.5 → 2.0.0-beta.7

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.
@@ -81,6 +81,12 @@ export interface PublicHelloResult {
81
81
  httpStatus?: number;
82
82
  secretDiverged?: boolean;
83
83
  }
84
+ export interface PublicHelloOptions {
85
+ rotate: boolean;
86
+ evolverVersion?: string;
87
+ /** Keep trust probes from adopting or clearing local credential state. */
88
+ preserveCredentials?: boolean;
89
+ }
84
90
  export interface PublicHeartbeatOptions {
85
91
  evolverVersion?: string;
86
92
  lastUpdate?: PublicLastUpdatePayload;
@@ -129,10 +135,7 @@ export declare class PublicHubCapability implements hub.HubCapability {
129
135
  readonly auth: hub.AuthProvider;
130
136
  readonly recipes: hub.RecipeCapability;
131
137
  constructor(opts: PublicHubOptions);
132
- hello(opts: {
133
- rotate: boolean;
134
- evolverVersion?: string;
135
- }): Promise<PublicHelloResult>;
138
+ hello(opts: PublicHelloOptions): Promise<PublicHelloResult>;
136
139
  heartbeat(opts?: PublicHeartbeatOptions): Promise<PublicHeartbeatResult>;
137
140
  private heartbeatMeta;
138
141
  publish(bundle: hub.AssetRecord[]): Promise<hub.PublishReceipt>;
@@ -1,4 +1,4 @@
1
- import { hub as hubNs } from '@evomap/evolver-core';
1
+ import { bootstrap, hub as hubNs } from '@evomap/evolver-core';
2
2
  import { AuthError, HubFetch, HubClientError, isHubUnreachableError } from './hubFetch.js';
3
3
  import { isNodeSecret, parseNodeSecretVersion } from './auth/legacyShim.js';
4
4
  import { inboundToAgentEvent, agentEventToOutbound, publishRespToReceipt, searchQueryToFetchWire } from './wireMap.js';
@@ -103,6 +103,11 @@ export class PublicHubCapability {
103
103
  timestamp: new Date().toISOString(),
104
104
  ...(sender ? { node_id: sender } : {}),
105
105
  ...(opts.evolverVersion ? { evolver_version: opts.evolverVersion } : {}),
106
+ // v1 parity (a2aProtocol.js buildHello): every hello carries the env fingerprint — it is how the
107
+ // hub builds node/IP trust for its anti-abuse layer. v2 had moved it to heartbeat-only meta, which
108
+ // one-shot CLI paths never send; the hub then answers heartbeats with resend_hello
109
+ // `missing_env_fingerprint` and 403-antibodies /a2a/fetch (#555).
110
+ env_fingerprint: bootstrap.captureEnvFingerprint({ env: this.opts.antiAbuse?.env ?? process.env }),
106
111
  }));
107
112
  const payload = asRecord(body['payload']) ?? body;
108
113
  const retryAfterMs = numberField(payload, 'retry_after_ms') ?? numberField(payload, 'retryAfterMs');
@@ -115,10 +120,12 @@ export class PublicHubCapability {
115
120
  // cleanly; retrying with the diverged secret never can. Signals the caller NOT to arm reauth
116
121
  // backoff. Only legacy node_secret auth exposes this hook — enterprise_token is a no-op.
117
122
  if (isSecretDivergenceRejection(body, payload)) {
118
- this.auth.notifyNodeSecretDiverged?.();
123
+ if (!opts.preserveCredentials) {
124
+ this.auth.notifyNodeSecretDiverged?.();
125
+ }
119
126
  return {
120
127
  ok: false,
121
- error: 'secret_diverged_cleared',
128
+ error: opts.preserveCredentials ? 'secret_diverged' : 'secret_diverged_cleared',
122
129
  secretDiverged: true,
123
130
  ...(rateLimitUntilMs !== undefined ? { rateLimitUntilMs } : {}),
124
131
  ...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
@@ -134,11 +141,13 @@ export class PublicHubCapability {
134
141
  ?? this.opts.senderId();
135
142
  const nodeSecret = stringField(payload, 'node_secret') ?? stringField(payload, 'nodeSecret');
136
143
  const nodeSecretVersion = parseNodeSecretVersion(payload['node_secret_version'] ?? payload['nodeSecretVersion']);
137
- if (nodeSecret && isNodeSecret(nodeSecret)) {
138
- this.auth.adoptNodeSecret?.(nodeSecret, nodeSecretVersion);
139
- }
140
- else {
141
- this.auth.adoptNodeSecretVersion?.(nodeSecretVersion);
144
+ if (!opts.preserveCredentials) {
145
+ if (nodeSecret && isNodeSecret(nodeSecret)) {
146
+ this.auth.adoptNodeSecret?.(nodeSecret, nodeSecretVersion);
147
+ }
148
+ else {
149
+ this.auth.adoptNodeSecretVersion?.(nodeSecretVersion);
150
+ }
142
151
  }
143
152
  return {
144
153
  ok: payload['ok'] !== false && Boolean(nodeId),
@@ -710,7 +719,38 @@ function assetsFromBody(body) {
710
719
  ...(Array.isArray(payload?.['assets']) ? payload['assets'] : []),
711
720
  ...(Array.isArray(payload?.['results']) ? payload['results'] : []),
712
721
  ];
713
- return candidates.filter((candidate) => Boolean(candidate && typeof candidate === 'object' && !Array.isArray(candidate)));
722
+ return candidates
723
+ .filter((candidate) => Boolean(candidate && typeof candidate === 'object' && !Array.isArray(candidate)))
724
+ .map(unwrapFetchDeliveryRow);
725
+ }
726
+ // Delivery-row ranking metadata carried over onto the unwrapped GEP record: hubReuse's candidate
727
+ // ranking reads these from the row (toRankedCandidate), and reuse's ingest strips them before
728
+ // hashing, so carrying them is both useful and integrity-safe. Do not carry `confidence`: it is
729
+ // transport metadata on Gene rows but canonical content on Capsules, so overloading it here can
730
+ // either poison a Gene hash or overwrite the meaning of Capsule content (#565).
731
+ const FETCH_ROW_CARRYOVER_KEYS = ['gdi_score', 'success_rate', 'reuse_count', 'source_node_id'];
732
+ /**
733
+ * The live hub's /a2a/fetch results are DELIVERY ROWS, not raw GEP records (#565, observed on
734
+ * evomap.ai 2026-07-22): the record itself nests under `payload`, while the row's own keys are
735
+ * delivery metadata (asset_type, bundle_id, confidence, gdi_score_mean, callable, …). Treating the
736
+ * row as the asset made reuse's integrity check (computeAssetId over the row) fail on every
737
+ * delivered asset. A GEP record always carries a string `type`; delivery rows carry `asset_type`
738
+ * instead — so unwrap exactly when the row has no `type` and nests an object payload that looks
739
+ * like a GEP record. Rows that already ARE raw records (older hubs, tests) pass through unchanged.
740
+ */
741
+ function unwrapFetchDeliveryRow(row) {
742
+ const record = row;
743
+ if (typeof record['type'] === 'string')
744
+ return row;
745
+ const inner = asRecord(record['payload']);
746
+ if (!inner || typeof inner['type'] !== 'string' || typeof inner['asset_id'] !== 'string')
747
+ return row;
748
+ const carryover = {};
749
+ for (const key of FETCH_ROW_CARRYOVER_KEYS) {
750
+ if (record[key] !== undefined && inner[key] === undefined)
751
+ carryover[key] = record[key];
752
+ }
753
+ return { ...inner, ...carryover };
714
754
  }
715
755
  function accountAssetsFromPayload(payload) {
716
756
  const candidates = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver-adapter-public",
3
- "version": "2.0.0-beta.5",
3
+ "version": "2.0.0-beta.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "公版 hub 适配器 (积分/治理)",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@evomap/atp-sdk": "^0.1.0",
17
- "@evomap/evolver-core": "2.0.0-beta.5",
17
+ "@evomap/evolver-core": "2.0.0-beta.7",
18
18
  "undici": "^6.27.0"
19
19
  },
20
20
  "optionalDependencies": {