588 {
593 if (request.submission_ref.empty() || request.title.empty()) {
594 throw std::invalid_argument(
595 "submission reference and title are required"
596 );
597 }
598 if (inbox == inbox.root_path() || !inbox.has_parent_path()) {
599 throw std::invalid_argument("inbox must be a scoped directory");
600 }
602 throw std::invalid_argument(
603 "operational ledger must remain outside the internal batch queue"
604 );
605 }
609 throw std::invalid_argument(
610 "internal queue and external legacy inbox must not overlap"
611 );
612 }
613 std::filesystem::create_directories(inbox);
616 source, request.max_payload_bytes, "submission"
617 );
618 const std::string& payload_hash = source_fingerprint.sha256;
619 const std::string envelope_id = "env_"
621 "batch_envelope_v1\n" + payload_hash + "\n"
622 + request.submission_ref
623 )
624 .substr(0, 32);
625
626 try {
627 auto existing =
get(envelope_id);
628 if (existing.payload_sha256 != payload_hash
629 || existing.submission_ref != request.submission_ref
630 || existing.title != request.title
631 || existing.supersedes != request.supersedes) {
632 throw std::logic_error("stable envelope identity collision");
633 }
635 throw std::runtime_error(
636 "registered cocoon payload escaped the internal queue"
637 );
638 }
640 existing.payload_ref, "registered cocoon payload"
641 );
642 std::error_code retained_error;
643 const auto retained_status = std::filesystem::symlink_status(
644 existing.payload_ref, retained_error
645 );
646 if (retained_status.type() == std::filesystem::file_type::not_found
647 || retained_error == std::errc::no_such_file_or_directory) {
649
650
651 return existing;
652 }
653 throw std::runtime_error(
654 "queued cocoon payload disappeared before integration"
655 );
656 }
658 existing.payload_ref, request.max_payload_bytes,
659 "registered cocoon payload"
660 );
661 if (retained.sha256 != existing.payload_sha256
662 || retained.byte_length != existing.byte_length) {
663 throw std::runtime_error(
664 "registered internal queue payload was modified"
665 );
666 }
669 existing.payload_ref, request.max_payload_bytes,
670 "registered cocoon payload"
671 );
672 if (after_validation.sha256 != retained.sha256
673 || after_validation.byte_length != retained.byte_length) {
674 throw std::runtime_error(
675 "registered inbox payload changed during validation"
676 );
677 }
679 try {
681 envelope_id, target, "arachne:intake",
682 "opaque payload received and queued for explicit approval"
683 );
684 } catch (const std::logic_error&) {
685 existing =
get(envelope_id);
686 if (existing.status == target) {
687 return existing;
688 }
689 throw;
690 }
691 }
692 return existing;
693 } catch (const std::out_of_range&) { }
694
695 std::filesystem::path payload_ref;
696 file_fingerprint retained;
698 payload_ref = source;
699 retained = source_fingerprint;
700 } else {
701 auto normalized_request = request;
702 normalized_request.inbox_root = inbox;
704 const auto staging_root
705 = inbox.parent_path() / ".arachne-intake-staging";
707 std::filesystem::create_directories(staging_root);
710 const auto staging = staging_root
711 / (envelope_id +
"-" + std::to_string(
unix_seconds()) +
"-"
712 + std::to_string(sequence) + ".part");
713 try {
714 std::filesystem::copy_file(
715 source, staging, std::filesystem::copy_options::none
716 );
718 staging, request.max_payload_bytes, "staged inbox copy"
719 );
720 if (staged.sha256 != payload_hash
721 || staged.byte_length != source_fingerprint.byte_length) {
722 throw std::runtime_error("inbox copy hash or size mismatch");
723 }
724
725
726 std::filesystem::create_hard_link(staging, payload_ref);
727 if (!std::filesystem::remove(staging)) {
728 throw std::runtime_error("cannot retire intake staging link");
729 }
730 } catch (...) {
731 std::error_code ignored;
732 std::filesystem::remove(staging, ignored);
733 throw;
734 }
736 payload_ref, request.max_payload_bytes, "retained inbox payload"
737 );
738 if (retained.sha256 != payload_hash
739 || retained.byte_length != source_fingerprint.byte_length) {
740 throw std::runtime_error(
741 "retained inbox payload changed during publication"
742 );
743 }
744 }
745
747 payload_ref, request.max_payload_bytes, "retained inbox payload"
748 );
749 if (final_fingerprint.sha256 != retained.sha256
750 || final_fingerprint.byte_length != retained.byte_length) {
751 throw std::runtime_error(
752 "retained inbox payload changed during mechanical validation"
753 );
754 }
755 retained = final_fingerprint;
757 sqlite3* database =
impl_->database.get();
758 execute(database,
"BEGIN IMMEDIATE;");
759 try {
760 auto statement =
prepare(database, R
"SQL(
761INSERT INTO envelopes(
762 envelope_id, payload_ref, payload_sha256, format_version,
763 submission_ref, title, supersedes, byte_length, created_at
764) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
765)SQL");
766 bind_text(database, statement.get(), 1, envelope_id);
768 database, statement.get(), 2,
770 );
771 bind_text(database, statement.get(), 3, payload_hash);
772 if (sqlite3_bind_int(statement.get(), 4, 0) != SQLITE_OK) {
774 }
775 bind_text(database, statement.get(), 5, request.submission_ref);
776 bind_text(database, statement.get(), 6, request.title);
777 bind_optional(database, statement.get(), 7, request.supersedes);
778 if (sqlite3_bind_int64(
779 statement.get(), 8,
780 static_cast<sqlite3_int64>(retained.byte_length)
781 )
782 != SQLITE_OK) {
784 }
785 bind_text(database, statement.get(), 9, created_at);
787
788 auto state_statement =
prepare(database, R
"SQL(
789INSERT INTO envelope_state(envelope_id, status, accepted_by, updated_at)
790VALUES (?, 'received', NULL, ?)
791)SQL");
792 bind_text(database, state_statement.get(), 1, envelope_id);
793 bind_text(database, state_statement.get(), 2, created_at);
794 step_done(database, state_statement.get());
796 } catch (...) {
797 execute(database,
"ROLLBACK;");
envelope_record transition(std::string_view envelope_id, cocoon_status next, std::string_view actor_ref, std::string_view reason={})
std::atomic< std::uint64_t > intake_stage_sequence
void bind_optional(sqlite3 *database, sqlite3_stmt *statement, int index, const std::optional< std::string > &value)
std::filesystem::path choose_destination(const intake_request &request, std::string_view payload_hash)
std::string portable_payload_reference(const std::filesystem::path &payload, const std::filesystem::path &ledger_path)
std::string sha256(std::span< const std::byte > bytes)