Просмотр исходного кода

Push a fresh account image as soon as a Triumph is claimed

A claim wrote its flag into the claim store, but nothing told the peer to read the account again,
so the Triumph only turned Acquired once some unrelated action happened to stage a Family-4
refresh. Dismantling an item worked; claiming alone did not.

Send the account image with the claim. A claim needs no transaction: it has already written the
store, it changes account object bytes rather than the manifest, and there is nothing to commit
afterwards. So it travels as a flag on the outcome and picks up
append_account_resync_notification, which is exactly one full account snapshot at the peer's next
Family-4 version. A snapshot that cannot be built is logged and the ladder is left alone, matching
how the change-character path treats the same failure.

Report where the record extraction stops. Without it a failed extraction looked like a broken
claim three layers downstream: the domain stayed empty, every claim failed on record_definition,
and nothing said why.

Measured: claiming record 221 resolves flag index 9144 and a 179-object family-4 resync leaves
4 ms later, and the Triumph turns Acquired with no second action.

Known gap: the records domain is not written to build_data.bin, so it is only populated on a
cache miss. On a warm cache the extraction never runs and claims fail on record_definition.
Millie 3 недель назад
Родитель
Сommit
155dc4b264

+ 19 - 0
Sunrise/src/client/content/items/packages/package_record_build.cpp

@@ -1,11 +1,27 @@
+#include <array>
+#include <cstdio>
 #include <cstring>
 
+#include "../../../../core/logging/log.h"
+
 #include "../../../../state/build_data/runtime.h"
 #include "internal.h"
 
 namespace sunrise::client::content::items::packages {
 namespace {
 
+/** Reports where the record pass stopped, so a silent miss cannot look like a working claim. */
+void report(const char* stage, unsigned long long detail) noexcept {
+    std::array<char, 128> line{};
+    const int count = std::snprintf(
+        line.data(), line.size(), "ev=pkg stage=records result=%s detail=%llu", stage, detail);
+    if (count > 0) {
+        core::log::write(core::log::Channel::client,
+                         core::log::Level::info,
+                         {line.data(), static_cast<std::size_t>(count)});
+    }
+}
+
 /** A record with no completion flag carries a non-positive slot, which addresses nothing. */
 [[nodiscard]] constexpr bool addressable_slot(std::int16_t slot) noexcept {
     return slot > 0;
@@ -43,6 +59,7 @@ bool build_records(const reader::Source& source,
         || mapRows.dataOffset
                    + static_cast<std::size_t>(mapRows.count) * tables::kUnlockMapRowStride
                > blob.size()) {
+        report("flag_map_fail", mapTag);
         return false;
     }
 
@@ -76,6 +93,7 @@ bool build_records(const reader::Source& source,
         || rows.count == 0 || rows.count > output.size()
         || rows.dataOffset + static_cast<std::size_t>(rows.count) * tables::kRecordRowStride
                > blob.size()) {
+        report("record_table_fail", tableTag);
         return false;
     }
 
@@ -94,6 +112,7 @@ bool build_records(const reader::Source& source,
         }
         ++count;
     }
+    report("ok", static_cast<unsigned long long>(count));
     return count != 0;
 }
 

+ 1 - 0
Sunrise/src/server/bap/encrypted/body/bap_service_body.cpp

@@ -198,6 +198,7 @@ bool process(const ServiceRoute& route,
             return false;
         }
         outcome.hasSubscription = webOutcome.hasSubscription;
+        outcome.hasRecordClaim = webOutcome.hasRecordClaim;
         outcome.subscription = webOutcome.subscription;
         const auto* equipmentSwap =
             web_service::mutation_if<state::PendingEquipmentSwap>(webOutcome);

+ 2 - 0
Sunrise/src/server/bap/encrypted/internal.h

@@ -79,6 +79,8 @@ struct ItemDismantleTransaction {
 /** Optional side effect produced while decoding one authenticated service body. */
 struct ServiceOutcome {
     bool hasSubscription{};
+    /** A Triumph claim changed the account flag bank and its image has to follow. */
+    bool hasRecordClaim{};
     middleware::queuez::Subscription subscription{};
     bool hasUnsubscription{};
     middleware::bap::family_unsubscription::Request unsubscription{};

+ 10 - 0
Sunrise/src/server/bap/encrypted/queuez/queuez_outcome_staging.cpp

@@ -369,6 +369,16 @@ bool stage_service_outcome(Scratch& scratch,
         }
         middleware::secure_channel::advance_nonce(nonce);
         after = acquisition.after;
+    } else if (outcome.hasRecordClaim) {
+        // A claim rewrites one byte of the account flag bank and leaves the manifest alone, so a
+        // full account snapshot at the next version carries it with no other staging.
+        if (!push::append_account_resync_notification(
+                scratch, before, key, nonce, response, written, after)) {
+            core::log::write(core::log::Channel::server,
+                             core::log::Level::warn,
+                             "ev=ws1801 stage=queuez_resync result=fail");
+            return true;
+        }
     } else if (itemDismantle != nullptr) {
         // A dismantle removes exactly one resident while preserving the relative order of every
         // survivor. The character after-image and empty release descriptor must fit together or

+ 2 - 1
Sunrise/src/server/web_service/web_service_actions.cpp

@@ -849,7 +849,6 @@ void claim_record(const middleware::web_service::Message& message, Outcome& outc
     // Deliberately no mutation. The shared reply path answers an action that prepares nothing with
     // the refusal status, so preparing a placeholder here would turn a silently-accepted claim into
     // an explicitly rejected one. Attach the transition here once claimed state is identified.
-    (void)outcome;
     namespace records = state::build_data::records;
     middleware::web_service::messages::opcode1801::Request request{};
     if (!middleware::web_service::messages::opcode1801::parse_request(message, request)) {
@@ -878,6 +877,8 @@ void claim_record(const middleware::web_service::Message& message, Outcome& outc
                             definition.completionFlagIndex);
         return;
     }
+    // The claim is already in the store, so the account image only has to be sent again.
+    outcome.hasRecordClaim = true;
     report_record_claim(
         message, "ok", "claimed", request.recordIndex, definition.completionFlagIndex);
 }

+ 2 - 0
Sunrise/src/server/web_service/web_service_runtime.h

@@ -14,6 +14,8 @@ namespace sunrise::server::web_service {
 struct Outcome {
     bool hasSubscription{};
     middleware::queuez::Subscription subscription{};
+    /** A claim changed the account flag bank, so a fresh account image has to follow. */
+    bool hasRecordClaim{};
     /** An opcode-504 pick moved the selection and its Family-4 object still has to follow. */
     bool hasSelectedCharacter{};
     std::uint64_t selectedCharacterSoid{};