← Memova.aiCONTENT HISTORY

Update to Memova.ai

Snapshot Oct 24, 2026 · 13:10 UTC · version 1.15.1

WHAT CHANGED

Package or technical metadata updated

Package or technical metadata updated

Observed in package metadata. These changes alone do not establish a new customer-facing feature.

Save Memova.ai and check future changes in Following.

Up to 3 plugins free · no card required.

All fields and original values.

These are saved file metadata. Open a changed file to read its text edits.

Package file

Before

Sep 30, 2026 · 23:00 UTC

333caf4b33faceae5e71a169c696a070f4ffbcf5392c83f22704ce8a8b058311

After

Oct 24, 2026 · 13:10 UTC

af9ff37fc68dfba75882c0f49b53ceb412fbd6924d3e97fe08efee3dedaffe77

Package file

Before

Sep 30, 2026 · 23:00 UTC

127

After

Oct 24, 2026 · 13:10 UTC

98

Package file

Before

Sep 30, 2026 · 23:00 UTC

b09aeecc839ac6fbdaffa93c985edcda36eae4a10cd319f4d2e306b772f83e4b

After

Oct 24, 2026 · 13:10 UTC

4d721d3b50a321c7069b5ab068b3709adbb9fb2fcbb57df56e857d7290ec7a5a

Package file

Before

Sep 30, 2026 · 23:00 UTC

1223

After

Oct 24, 2026 · 13:10 UTC

10908

Compare saved observations

Download comparison JSON

Changed files

Choose a file to see its text changes. Binary or large files show metadata only.

.app.json →

.codex-plugin/plugin.json →

.mcp.json →

assets/icon.svg →

assets/logo.svg →

contracts/resource_access_plugin_binding_v1.json →

contracts/upstream/memova_resource_access_v1.json →

menu_catalog.json →

scripts/agent_archive.py →

scripts/check_menu_catalog.py →

scripts/ensure_mcp_login.py →

scripts/kb_setup_reminder.py →

scripts/mcp_connection_auth.py →

scripts/mcp_credential_store.py →

scripts/memova_menu.py →

scripts/validate_resource_access_contract.py →

scripts/version_check.py →

skills/memova-agent-archive/SKILL.md →

skills/memova-agent-archive/agents/openai.yaml →

skills/memova-connect/SKILL.md →

skills/memova-connect/agents/openai.yaml →

skills/memova-explicit-import/SKILL.md →

skills/memova-explicit-import/agents/openai.yaml →

skills/memova-explicit-import/scripts/preview_selected_import.py →

skills/memova-knowledge/SKILL.md →

skills/memova-knowledge/agents/openai.yaml →

skills/memova-menu/SKILL.md →

skills/memova-menu/agents/openai.yaml →

skills/memova-personal-manual/SKILL.md →

skills/memova-personal-manual/agents/openai.yaml →

skills/memova-personal-manual/references/local-artifacts.md →

skills/memova-personal-manual/scripts/prepare_personal_manual.py →

skills/memova-resource-access/SKILL.md →

skills/memova-resource-access/agents/openai.yaml →

skills/memova-vault-diagnose/SKILL.md →

skills/memova-vault-diagnose/agents/openai.yaml →

skills/memova-vault-setup/SKILL.md →

skills/memova-vault-setup/agents/openai.yaml →

skills/memova-vault-setup/scripts/create_memova_vault.py →

skills/memova-vault-setup/scripts/diagnose_memova_vault.py →

skills/memova-vault-setup/scripts/find_vault_locations.py →

skills/memova-vault-setup/scripts/inspect_vault.py →

skills/memova-vault-setup/scripts/memova_vault_lib.py →

skills/memova-vault-setup/scripts/setup_fixture_harness.py →

skills/memova-vault-setup/scripts/validate_memova_vault.py →

skills/memova-workflow/SKILL.md →

skills/memova-workflow/agents/openai.yaml →

scripts/validate_resource_access_contract.py

− removed · + added

--- before
+++ after
@@ -0,0 +1,450 @@
+#!/usr/bin/env python3
+"""Fail-closed consumer gate for the Memova Resource Access V1 contract.
+
+The normal plugin CI validates a checked-in upstream snapshot so it does not depend on a
+developer-specific backend checkout. A release or integration job may pass a freshly exported
+backend contract with ``--upstream-contract``; that mode also requires the backend's public MCP
+contract version explicitly.
+"""
+
+from __future__ import annotations
+
+import argparse
+import ast
+import json
+import re
+import sys
+from pathlib import Path
+from typing import Any
+
+
+PLUGIN_ROOT = Path(__file__).resolve().parents[1]
+DEFAULT_BINDING = PLUGIN_ROOT / "contracts" / "resource_access_plugin_binding_v1.json"
+
+
+class ContractError(ValueError):
+    """Raised when a provider or plugin contract fails closed."""
+
+
+def _load_object(path: Path, label: str) -> dict[str, Any]:
+    try:
+        payload = json.loads(path.read_text(encoding="utf-8"))
+    except (OSError, json.JSONDecodeError) as exc:
+        raise ContractError(f"{label} is unreadable: {path}: {exc}") from exc
+    if not isinstance(payload, dict):
+        raise ContractError(f"{label} must be a JSON object: {path}")
+    return payload
+
+
+def _mapping(value: Any, label: str) -> dict[str, Any]:
+    if not isinstance(value, dict):
+        raise ContractError(f"{label} must be an object")
+    return value
+
+
+def _list(value: Any, label: str) -> list[Any]:
+    if not isinstance(value, list):
+        raise ContractError(f"{label} must be an array")
+    return value
+
+
+def _exact_keys(value: dict[str, Any], expected: set[str], label: str) -> None:
+    actual = set(value)
+    if actual != expected:
+        missing = sorted(expected - actual)
+        unknown = sorted(actual - expected)
+        raise ContractError(f"{label} keys changed; missing={missing}, unknown={unknown}")
+
+
+def _string_list(value: Any, label: str) -> list[str]:
+    items = _list(value, label)
+    if any(not isinstance(item, str) or not item for item in items):
+        raise ContractError(f"{label} must contain only non-empty strings")
+    if len(items) != len(set(items)):
+        raise ContractError(f"{label} must not contain duplicates")
+    return items
+
+
+def _semver(value: Any, label: str) -> tuple[int, int, int]:
+    if not isinstance(value, str):
+        raise ContractError(f"{label} must be a semantic version string")
+    match = re.fullmatch(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)", value)
+    if not match:
+        raise ContractError(f"{label} must be a stable three-part semantic version")
+    return tuple(int(part) for part in match.groups())
+
+
+def load_binding(path: Path = DEFAULT_BINDING) -> dict[str, Any]:
+    binding = _load_object(path, "plugin binding")
+    _exact_keys(
+        binding,
+        {
+            "schema_version",
+            "minimum_plugin_version",
+            "minimum_mcp_public_contract_version",
+            "upstream_snapshot",
+            "oauth_workflows",
+            "provider_projection",
+        },
+        "plugin binding",
+    )
+    if binding["schema_version"] != "memova_resource_access_plugin_binding_v1":
+        raise ContractError("unsupported plugin binding schema_version")
+    _semver(binding["minimum_plugin_version"], "minimum_plugin_version")
+    _semver(
+        binding["minimum_mcp_public_contract_version"],
+        "minimum_mcp_public_contract_version",
+    )
+    if not isinstance(binding["upstream_snapshot"], str) or not binding["upstream_snapshot"]:
+        raise ContractError("upstream_snapshot must be a non-empty relative path")
+    snapshot_path = Path(binding["upstream_snapshot"])
+    if snapshot_path.is_absolute():
+        raise ContractError("upstream_snapshot must not depend on an absolute checkout path")
+    binding_directory = path.resolve().parent
+    if not (binding_directory / snapshot_path).resolve().is_relative_to(binding_directory):
+        raise ContractError("upstream_snapshot must stay inside the binding contracts directory")
+
+    workflows = _mapping(binding["oauth_workflows"], "oauth_workflows")
+    _exact_keys(workflows, {"resource-read", "resource-download"}, "oauth_workflows")
+    for name, scopes in workflows.items():
+        _string_list(scopes, f"oauth_workflows.{name}")
+    projection = _mapping(binding["provider_projection"], "provider_projection")
+    access = _mapping(projection.get("access"), "provider_projection.access")
+    resources = _list(projection.get("resources"), "provider_projection.resources")
+    domain_scopes: list[str] = []
+    for resource in resources:
+        resource_mapping = _mapping(resource, "provider_projection resource")
+        for scope in _string_list(
+            resource_mapping.get("domain_read_scopes"),
+            "provider_projection resource domain_read_scopes",
+        ):
+            if scope not in domain_scopes:
+                domain_scopes.append(scope)
+    expected_read = [access.get("gateway_read_scope"), *domain_scopes]
+    expected_download = [
+        access.get("gateway_read_scope"),
+        access.get("download_additional_scope"),
+        *domain_scopes,
+    ]
+    if workflows["resource-read"] != expected_read:
+        raise ContractError(
+            "resource-read OAuth workflow does not match provider gateway and domain scopes"
+        )
+    if workflows["resource-download"] != expected_download:
+        raise ContractError(
+            "resource-download OAuth workflow does not match provider gateway, export, and domain scopes"
+        )
+    return binding
+
+
+def project_provider(provider: dict[str, Any]) -> dict[str, Any]:
+    """Project every provider field that the plugin relies on.
+
+    New tools, adapters, representations, or domain scopes remain in the projection and therefore
+    fail equality against the reviewed plugin binding until that binding is deliberately updated.
+    """
+
+    for field in (
+        "contract_version",
+        "boundary",
+        "descriptor_policy",
+        "access_policy",
+        "transfer_policy",
+        "resource_adapters",
+        "mcp_surface",
+    ):
+        if field not in provider:
+            raise ContractError(f"provider contract is missing {field}")
+
+    boundary = _mapping(provider["boundary"], "provider.boundary")
+    descriptor = _mapping(provider["descriptor_policy"], "provider.descriptor_policy")
+    access = _mapping(provider["access_policy"], "provider.access_policy")
+    transfer = _mapping(provider["transfer_policy"], "provider.transfer_policy")
+    mcp = _mapping(provider["mcp_surface"], "provider.mcp_surface")
+
+    tools: list[dict[str, Any]] = []
+    for index, value in enumerate(_list(mcp.get("tool_contracts"), "provider.mcp_surface.tool_contracts")):
+        tool = _mapping(value, f"provider.mcp_surface.tool_contracts[{index}]")
+        required = {
+            "name",
+            "required_input_fields",
+            "optional_input_fields",
+            "unknown_input_fields",
+            "latest_revision_allowed",
+        }
+        missing = required - set(tool)
+        if missing:
+            raise ContractError(f"provider tool contract is missing {sorted(missing)}")
+        tools.append(
+            {
+                "name": tool["name"],
+                "required_input_fields": _string_list(
+                    tool["required_input_fields"], f"provider tool {tool['name']} required fields"
+                ),
+                "optional_input_fields": _string_list(
+                    tool["optional_input_fields"], f"provider tool {tool['name']} optional fields"
+                ),
+                "unknown_input_fields": tool["unknown_input_fields"],
+                "latest_revision_allowed": tool["latest_revision_allowed"],
+            }
+        )
+    tools.sort(key=lambda item: str(item["name"]))
+    tool_names = [item["name"] for item in tools]
+    if len(tool_names) != len(set(tool_names)):
+        raise ContractError("provider tool names must be unique")
+
+    resources: list[dict[str, Any]] = []
+    for index, value in enumerate(_list(provider["resource_adapters"], "provider.resource_adapters")):
+        adapter = _mapping(value, f"provider.resource_adapters[{index}]")
+        resource_type = adapter.get("resource_type")
+        if not isinstance(resource_type, str) or not resource_type:
+            raise ContractError("provider adapter resource_type must be a non-empty string")
+        representations: list[dict[str, Any]] = []
+        for representation_value in _list(
+            adapter.get("representations"), f"provider adapter {resource_type} representations"
+        ):
+            representation = _mapping(
+                representation_value, f"provider adapter {resource_type} representation"
+            )
+            if not isinstance(representation.get("representation"), str) or not isinstance(
+                representation.get("mime_type"), str
+            ):
+                raise ContractError(
+                    f"provider adapter {resource_type} representations require representation and mime_type"
+                )
+            representations.append(
+                {
+                    "representation": representation["representation"],
+                    "mime_type": representation["mime_type"],
+                }
+            )
+        representations.sort(key=lambda item: item["representation"])
+        resources.append(
+            {
+                "resource_type": resource_type,
+                "domain_read_scopes": sorted(
+                    _string_list(
+                        adapter.get("domain_read_scopes"),
+                        f"provider adapter {resource_type} domain_read_scopes",
+                    )
+                ),
+                "representations": representations,
+            }
+        )
+    resources.sort(key=lambda item: item["resource_type"])
+    resource_types = [item["resource_type"] for item in resources]
+    if len(resource_types) != len(set(resource_types)):
+        raise ContractError("provider resource types must be unique")
+
+    projection = {
+        "contract_version": provider["contract_version"],
+        "standard_methods": _string_list(
+            mcp.get("standard_methods"), "provider.mcp_surface.standard_methods"
+        ),
+        "tools": tools,
+        "resources": resources,
+        "access": {
+            "gateway_read_scope": access.get("gateway_read_scope"),
+            "download_additional_scope": access.get("download_additional_scope"),
+            "gateway_scope_alone_grants_domain_access": access.get(
+                "gateway_scope_alone_grants_domain_access"
+            ),
+            "credential_types": _string_list(
+                access.get("credential_types"), "provider.access_policy.credential_types"
+            ),
+            "credential_revalidation": access.get("credential_revalidation"),
+            "scopes_expand_after_issue": access.get("scopes_expand_after_issue"),
+            "download_grant_binds_credential_type": access.get(
+                "download_grant_binds_credential_type"
+            ),
+        },
+        "transfer": {
+            "inline_text_max_bytes": transfer.get("inline_text_max_bytes"),
+            "inline_truncation": transfer.get("inline_truncation"),
+            "inline_oversize_behavior": transfer.get("inline_oversize_behavior"),
+            "download_url_ttl_seconds": transfer.get("download_url_ttl_seconds"),
+            "download_exact_revision_only": transfer.get("download_exact_revision_only"),
+        },
+        "safety": {
+            "raw_database_access": boundary.get("raw_database_access"),
+            "arbitrary_sql_access": boundary.get("arbitrary_sql_access"),
+            "write_operations": boundary.get("write_operations"),
+            "bulk_export": boundary.get("bulk_export"),
+            "untrusted_content": descriptor.get("untrusted_content"),
+        },
+    }
+    return projection
+
+
+def _tuple_constants(path: Path) -> dict[str, tuple[str, ...]]:
+    try:
+        tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
+    except (OSError, SyntaxError) as exc:
+        raise ContractError(f"OAuth helper cannot be parsed: {exc}") from exc
+    constants: dict[str, tuple[str, ...]] = {}
+    for node in tree.body:
+        if not isinstance(node, ast.Assign) or len(node.targets) != 1:
+            continue
+        target = node.targets[0]
+        if not isinstance(target, ast.Name) or not isinstance(node.value, (ast.Tuple, ast.List)):
+            continue
+        values: list[str] = []
+        for element in node.value.elts:
+            if not isinstance(element, ast.Constant) or not isinstance(element.value, str):
+                break
+            values.append(element.value)
+        else:
+            constants[target.id] = tuple(values)
+    return constants
+
+
+def validate_plugin(binding: dict[str, Any], plugin_root: Path = PLUGIN_ROOT) -> None:
+    manifest = _load_object(plugin_root / ".codex-plugin" / "plugin.json", "plugin manifest")
+    plugin_version = manifest.get("version")
+    if _semver(plugin_version, "plugin manifest version") < _semver(
+        binding["minimum_plugin_version"], "minimum_plugin_version"
+    ):
+        raise ContractError(
+            f"plugin version {plugin_version} is below Resource Access minimum "
+            f"{binding['minimum_plugin_version']}"
+        )
+
+    projection = _mapping(binding["provider_projection"], "provider_projection")
+    skill_path = plugin_root / "skills" / "memova-resource-access" / "SKILL.md"
+    try:
+        skill = skill_path.read_text(encoding="utf-8")
+    except OSError as exc:
+        raise ContractError(f"Resource Access skill is unreadable: {exc}") from exc
+
+    required_skill_tokens = {
+        binding["minimum_mcp_public_contract_version"],
+        str(projection["transfer"]["inline_text_max_bytes"]),
+        str(projection["transfer"]["download_url_ttl_seconds"]),
+        "five minutes",
+        "untrusted content",
+        "any required Memova tool is unavailable",
+        "scope-filtered tool catalog",
+    }
+    required_skill_tokens.update(tool["name"] for tool in projection["tools"])
+    required_skill_tokens.update(resource["resource_type"] for resource in projection["resources"])
+    required_skill_tokens.update(
+        representation["representation"]
+        for resource in projection["resources"]
+        for representation in resource["representations"]
+    )
+    required_skill_tokens.update(
+        scope for scopes in binding["oauth_workflows"].values() for scope in scopes
+    )
+    normalized_skill = skill.casefold().replace(",", "")
+    missing_tokens = sorted(
+        token for token in required_skill_tokens if str(token).casefold() not in normalized_skill
+    )
+    if missing_tokens:
+        raise ContractError(f"Resource Access skill is missing contract tokens: {missing_tokens}")
+
+    constants = _tuple_constants(plugin_root / "scripts" / "ensure_mcp_login.py")
+    expected_constants = {
+        "RESOURCE_READ_SCOPES": tuple(binding["oauth_workflows"]["resource-read"]),
+        "RESOURCE_DOWNLOAD_SCOPES": tuple(binding["oauth_workflows"]["resource-download"]),
+    }
+    for name, expected in expected_constants.items():
+        if constants.get(name) != expected:
+            raise ContractError(
+                f"OAuth helper {name} changed; expected={list(expected)}, "
+                f"actual={list(constants.get(name, ()))}"
+            )
+
+    connection_constants = _tuple_constants(
+        plugin_root / "scripts" / "mcp_connection_auth.py"
+    )
+    full_connection_scopes = set(connection_constants.get("FULL_MCP_SCOPES", ()))
+    required_connection_scopes = set(binding["oauth_workflows"]["resource-download"])
+    missing_connection_scopes = sorted(
+        required_connection_scopes.difference(full_connection_scopes)
+    )
+    if missing_connection_scopes:
+        raise ContractError(
+            "Connection Code and Agent Key helper is missing Resource Access scopes: "
+            f"{missing_connection_scopes}"
+        )
+
+
+def validate_bundle(
+    *,
+    binding_path: Path = DEFAULT_BINDING,
+    upstream_contract: Path | None = None,
+    upstream_mcp_public_contract_version: str | None = None,
+    plugin_root: Path = PLUGIN_ROOT,
+) -> dict[str, Any]:
+    binding = load_binding(binding_path)
+    if upstream_contract is None:
+        if upstream_mcp_public_contract_version is not None:
+            raise ContractError(
+                "--upstream-mcp-public-contract-version requires --upstream-contract"
+            )
+        provider_path = binding_path.parent / binding["upstream_snapshot"]
+        version_source = "checked_in_binding"
+    else:
+        if upstream_mcp_public_contract_version is None:
+            raise ContractError(
+                "fresh upstream validation requires --upstream-mcp-public-contract-version"
+            )
+        if _semver(
+            upstream_mcp_public_contract_version,
+            "upstream_mcp_public_contract_version",
+        ) < _semver(
+            binding["minimum_mcp_public_contract_version"],
+            "minimum_mcp_public_contract_version",
+        ):
+            raise ContractError(
+                "backend MCP public contract version is below the plugin minimum: "
+                f"minimum={binding['minimum_mcp_public_contract_version']}, "
+                f"actual={upstream_mcp_public_contract_version}"
+            )
+        provider_path = upstream_contract
+        version_source = "explicit_upstream"
+
+    provider = _load_object(provider_path, "provider contract")
+    actual_projection = project_provider(provider)
+    expected_projection = binding["provider_projection"]
+    if actual_projection != expected_projection:
+        raise ContractError(
+            "provider projection differs from the reviewed plugin binding:\n"
+            f"expected={json.dumps(expected_projection, ensure_ascii=False, sort_keys=True)}\n"
+            f"actual={json.dumps(actual_projection, ensure_ascii=False, sort_keys=True)}"
+        )
+    validate_plugin(binding, plugin_root)
+    return {
+        "ok": True,
+        "binding_schema_version": binding["schema_version"],
+        "provider_contract_version": actual_projection["contract_version"],
+        "minimum_mcp_public_contract_version": binding[
+            "minimum_mcp_public_contract_version"
+        ],
+        "minimum_plugin_version": binding["minimum_plugin_version"],
+        "provider_source": str(provider_path),
+        "version_source": version_source,
+    }
+
+
+def main(argv: list[str] | None = None) -> int:
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("--binding", type=Path, default=DEFAULT_BINDING)
+    parser.add_argument("--upstream-contract", type=Path)
+    parser.add_argument("--upstream-mcp-public-contract-version")
+    args = parser.parse_args(argv)
+    try:
+        result = validate_bundle(
+            binding_path=args.binding,
+            upstream_contract=args.upstream_contract,
+            upstream_mcp_public_contract_version=args.upstream_mcp_public_contract_version,
+        )
+    except ContractError as exc:
+        print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False), file=sys.stderr)
+        return 1
+    print(json.dumps(result, ensure_ascii=False, sort_keys=True))
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())
Full technical diff · 49 changed fields

changed /files/.app.json/sha256

BEFORE
"333caf4b33faceae5e71a169c696a070f4ffbcf5392c83f22704ce8a8b058311"
AFTER
"af9ff37fc68dfba75882c0f49b53ceb412fbd6924d3e97fe08efee3dedaffe77"

changed /files/.app.json/size

BEFORE
127
AFTER
98

changed /files/.codex-plugin~1plugin.json/sha256

BEFORE
"b09aeecc839ac6fbdaffa93c985edcda36eae4a10cd319f4d2e306b772f83e4b"
AFTER
"4d721d3b50a321c7069b5ab068b3709adbb9fb2fcbb57df56e857d7290ec7a5a"

changed /files/.codex-plugin~1plugin.json/size

BEFORE
1223
AFTER
10908

added /files/.mcp.json

BEFORE
Field was absent
AFTER
{
  "sha256": "be8708abebf3e796509ad32a83d2a62c427ace502f3d662daac054eece711903",
  "size": 89
}

added /files/assets~1icon.svg

BEFORE
Field was absent
AFTER
{
  "sha256": "54bd50b6408ef5c658fe724373919e3edbb220232ebd6938ab69c0517f72e7f7",
  "size": 685
}

added /files/assets~1logo.svg

BEFORE
Field was absent
AFTER
{
  "sha256": "427a49d8196f8daf05e6f948d43670f801040e3596261d7be902cdd6223897db",
  "size": 866
}

added /files/contracts~1resource_access_plugin_binding_v1.json

BEFORE
Field was absent
AFTER
{
  "sha256": "ca0e91ab63d1d80d92593d24db60e62911e9c96c1316e07391654b66f274e9be",
  "size": 3406
}

added /files/contracts~1upstream~1memova_resource_access_v1.json

BEFORE
Field was absent
AFTER
{
  "sha256": "6bef5e8c35934a2ff9d6a0dd55c7d70470ced995337f28f6c3b083ed28a4c78d",
  "size": 9185
}

added /files/menu_catalog.json

BEFORE
Field was absent
AFTER
{
  "sha256": "894363bbecb738df6aa6cea91dac994d381067049d4d866930a065cd1c6945b3",
  "size": 2509
}

added /files/scripts~1agent_archive.py

BEFORE
Field was absent
AFTER
{
  "sha256": "7ef5fe9d8292847c59543395104ec80709aedd56a992718ecefec793dd1f67c9",
  "size": 15116
}

added /files/scripts~1check_menu_catalog.py

BEFORE
Field was absent
AFTER
{
  "sha256": "d89afb741c7f73a2ac3857f3379268f00c61eec05ae2e8700738e440477bbe2f",
  "size": 638
}

added /files/scripts~1ensure_mcp_login.py

BEFORE
Field was absent
AFTER
{
  "sha256": "dd2ce35ecaa4ce7ec8f249ab962660f134515f9adcb5d714dd5e33222039bf92",
  "size": 20246
}

added /files/scripts~1kb_setup_reminder.py

BEFORE
Field was absent
AFTER
{
  "sha256": "cdb69e55bcf1faa65fa6664a4bd6a91abf7f9ab11db0ff7be5245c4c2a713fe5",
  "size": 5470
}

added /files/scripts~1mcp_connection_auth.py

BEFORE
Field was absent
AFTER
{
  "sha256": "3df8962e9d5c66a5298d9a9e6cbdc4d17755cc9d73d2f139bc3ee26256ed9bc2",
  "size": 21361
}

added /files/scripts~1mcp_credential_store.py

BEFORE
Field was absent
AFTER
{
  "sha256": "106077ee2270b0168ff15a7caded91da9fad252f1c1164b6e1d4228028b1712b",
  "size": 13043
}

added /files/scripts~1memova_menu.py

BEFORE
Field was absent
AFTER
{
  "sha256": "a31d626d81f92189782138cd438a7c070e4bb0104d4a47ad2024013816cf7c66",
  "size": 2503
}

added /files/scripts~1validate_resource_access_contract.py

BEFORE
Field was absent
AFTER
{
  "sha256": "130b4b179e301d7b0984822721c1c9633ff824c7e00f280f89fb98f1aebe486e",
  "size": 19271
}

added /files/scripts~1version_check.py

BEFORE
Field was absent
AFTER
{
  "sha256": "431220554511a5ffc66b14ac414259dbc50f24e97a050358fb35e1cc19b4c72e",
  "size": 10129
}

added /files/skills~1memova-agent-archive~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "f5acafcc0aded82b7173384f3be02bf2be31ab85b0842a15828c29e40c5539d1",
  "size": 6335
}

added /files/skills~1memova-agent-archive~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "080bd0c786e329c6538576681bc233bebcc0419dfa0dc34e78a7dd056bd46fec",
  "size": 463
}

added /files/skills~1memova-connect~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "8254a65d2ebe8a9e518fc0be28b0494a3b002e61d2b540812579fdf2b04ac19b",
  "size": 3181
}

added /files/skills~1memova-connect~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "7265de61625ee0b57733e64ab4807ac422a15bfd96a35b609f012c666cba3d71",
  "size": 248
}

added /files/skills~1memova-explicit-import~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "a9846d3d749b53a822c2b955cb75974dee16bfea84c33be3e2aba4170b6a318e",
  "size": 7700
}

added /files/skills~1memova-explicit-import~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "eb925da8a56261a8efb3869710fdba83fd4641da830acb3544c2c17145c4c115",
  "size": 483
}

added /files/skills~1memova-explicit-import~1scripts~1preview_selected_import.py

BEFORE
Field was absent
AFTER
{
  "sha256": "f4f01224c0b245dcb7ec5021714162293c758b8a1b7ec7d468ac1b197d9d06fb",
  "size": 7909
}

added /files/skills~1memova-knowledge~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "08da7d9d3589124da1d35524587ef2080c2455d191bb86ee4ed528695bcdf083",
  "size": 5445
}

added /files/skills~1memova-knowledge~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "5b2b81d6559f0d588fb352b303082607953d0cac2b2bc093210862253c68998a",
  "size": 496
}

added /files/skills~1memova-menu~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "88e9ce6c3a261ae5e6f633228e6f7188d2c7884abf15efc8941f417d1d23fc5e",
  "size": 8933
}

added /files/skills~1memova-menu~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "a3543b03bd48af221f0f0354f37fc32d5062bbf38c34993d8dad4cf3be05b1ee",
  "size": 204
}

added /files/skills~1memova-personal-manual~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "5e2ee8b0ae48ac8a469dc61de899e515e0356f48115cd7fa6371cb7a6d2f9b7d",
  "size": 11321
}

added /files/skills~1memova-personal-manual~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "409d332354b3bfbbd1a30e2262ad44cb393774360e067d359c196ccd4dbf7d9a",
  "size": 536
}

added /files/skills~1memova-personal-manual~1references~1local-artifacts.md

BEFORE
Field was absent
AFTER
{
  "sha256": "b7aa91d7a130338792a57cbe2937aea23fe6c94f7389dee3c7bd01035854dfa6",
  "size": 1914
}

added /files/skills~1memova-personal-manual~1scripts~1prepare_personal_manual.py

BEFORE
Field was absent
AFTER
{
  "sha256": "51cd409550a765c45b0fa3e78c9eb7309d308c769ac5aa83443f46616644bcc8",
  "size": 19703
}

added /files/skills~1memova-resource-access~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "360b19832d8891f20b05a0ec0d6eca849b4afb80915909edc7596fc335b932b3",
  "size": 13511
}

added /files/skills~1memova-resource-access~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "8f089f456a2cf60ce08dd3f006f8c835255c1b6b7f2f07af54aedc7c75c42d08",
  "size": 580
}

added /files/skills~1memova-vault-diagnose~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "b4c38e582baeb21e69ec8b04d5efa690739989a38c82468d1d3ad19465203a34",
  "size": 4809
}

added /files/skills~1memova-vault-diagnose~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "cc32fa628fb4f5c3c91f750dcf72bfdf4704fda2efb33dd1281f11b5ecaf49b6",
  "size": 469
}

added /files/skills~1memova-vault-setup~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "3c4410dac28396080939f749581e42a7ce67fa704975f7d4b19aa8eee31d4537",
  "size": 12497
}

added /files/skills~1memova-vault-setup~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "0c1cbe0d82408b2b2c885f90b70d0a85bcb34ec0ac20a17386c5220af5189841",
  "size": 475
}

added /files/skills~1memova-vault-setup~1scripts~1create_memova_vault.py

BEFORE
Field was absent
AFTER
{
  "sha256": "3272b0f7f648e402f5035e5f10b75ca18fd143c86031fa470bd162a17e054c22",
  "size": 4151
}

added /files/skills~1memova-vault-setup~1scripts~1diagnose_memova_vault.py

BEFORE
Field was absent
AFTER
{
  "sha256": "b4d6501f56f865473cda4aa7ca188735338011f7c962795bd0b4145a1e8e95a8",
  "size": 8060
}

added /files/skills~1memova-vault-setup~1scripts~1find_vault_locations.py

BEFORE
Field was absent
AFTER
{
  "sha256": "850c56ae9b5aa729964d2eb7e22a0ff7d37fd8d2fe89e0e6bc6d469caa6c5bd6",
  "size": 1965
}

added /files/skills~1memova-vault-setup~1scripts~1inspect_vault.py

BEFORE
Field was absent
AFTER
{
  "sha256": "7515a4d226c3a8acecfc662fb573e64a136f60aedb2460f6ed8afef212abee18",
  "size": 748
}

added /files/skills~1memova-vault-setup~1scripts~1memova_vault_lib.py

BEFORE
Field was absent
AFTER
{
  "sha256": "0d7977b3485222bbebf7cf6520c01b0c55712679a7939c30689b729bcbe23bef",
  "size": 97146
}

added /files/skills~1memova-vault-setup~1scripts~1setup_fixture_harness.py

BEFORE
Field was absent
AFTER
{
  "sha256": "605d8d19a51b551611bca2cdd612651e27a490a66d9f9b5958d39d876e27e3a6",
  "size": 50807
}

added /files/skills~1memova-vault-setup~1scripts~1validate_memova_vault.py

BEFORE
Field was absent
AFTER
{
  "sha256": "0c0632e1a8e192fae3b9802501cc298a2c3887ce63a552a469e32b83d6737269",
  "size": 2681
}

added /files/skills~1memova-workflow~1SKILL.md

BEFORE
Field was absent
AFTER
{
  "sha256": "7b98b4534cdec921a6b82f422f445a46d3a4f78cbb4feeabdfee1b3b924cb264",
  "size": 10156
}

added /files/skills~1memova-workflow~1agents~1openai.yaml

BEFORE
Field was absent
AFTER
{
  "sha256": "ade51e2a4a35f1ce6b1d05bda9d183ce58a10aedeab183bedc6382fbe5b47bbd",
  "size": 445
}
Full snapshot data
{
  "files": {
    ".app.json": {
      "sha256": "af9ff37fc68dfba75882c0f49b53ceb412fbd6924d3e97fe08efee3dedaffe77",
      "size": 98
    },
    ".codex-plugin/plugin.json": {
      "sha256": "4d721d3b50a321c7069b5ab068b3709adbb9fb2fcbb57df56e857d7290ec7a5a",
      "size": 10908
    },
    ".mcp.json": {
      "sha256": "be8708abebf3e796509ad32a83d2a62c427ace502f3d662daac054eece711903",
      "size": 89
    },
    "assets/icon.svg": {
      "sha256": "54bd50b6408ef5c658fe724373919e3edbb220232ebd6938ab69c0517f72e7f7",
      "size": 685
    },
    "assets/logo.svg": {
      "sha256": "427a49d8196f8daf05e6f948d43670f801040e3596261d7be902cdd6223897db",
      "size": 866
    },
    "contracts/resource_access_plugin_binding_v1.json": {
      "sha256": "ca0e91ab63d1d80d92593d24db60e62911e9c96c1316e07391654b66f274e9be",
      "size": 3406
    },
    "contracts/upstream/memova_resource_access_v1.json": {
      "sha256": "6bef5e8c35934a2ff9d6a0dd55c7d70470ced995337f28f6c3b083ed28a4c78d",
      "size": 9185
    },
    "menu_catalog.json": {
      "sha256": "894363bbecb738df6aa6cea91dac994d381067049d4d866930a065cd1c6945b3",
      "size": 2509
    },
    "scripts/agent_archive.py": {
      "sha256": "7ef5fe9d8292847c59543395104ec80709aedd56a992718ecefec793dd1f67c9",
      "size": 15116
    },
    "scripts/check_menu_catalog.py": {
      "sha256": "d89afb741c7f73a2ac3857f3379268f00c61eec05ae2e8700738e440477bbe2f",
      "size": 638
    },
    "scripts/ensure_mcp_login.py": {
      "sha256": "dd2ce35ecaa4ce7ec8f249ab962660f134515f9adcb5d714dd5e33222039bf92",
      "size": 20246
    },
    "scripts/kb_setup_reminder.py": {
      "sha256": "cdb69e55bcf1faa65fa6664a4bd6a91abf7f9ab11db0ff7be5245c4c2a713fe5",
      "size": 5470
    },
    "scripts/mcp_connection_auth.py": {
      "sha256": "3df8962e9d5c66a5298d9a9e6cbdc4d17755cc9d73d2f139bc3ee26256ed9bc2",
      "size": 21361
    },
    "scripts/mcp_credential_store.py": {
      "sha256": "106077ee2270b0168ff15a7caded91da9fad252f1c1164b6e1d4228028b1712b",
      "size": 13043
    },
    "scripts/memova_menu.py": {
      "sha256": "a31d626d81f92189782138cd438a7c070e4bb0104d4a47ad2024013816cf7c66",
      "size": 2503
    },
    "scripts/validate_resource_access_contract.py": {
      "sha256": "130b4b179e301d7b0984822721c1c9633ff824c7e00f280f89fb98f1aebe486e",
      "size": 19271
    },
    "scripts/version_check.py": {
      "sha256": "431220554511a5ffc66b14ac414259dbc50f24e97a050358fb35e1cc19b4c72e",
      "size": 10129
    },
    "skills/memova-agent-archive/SKILL.md": {
      "sha256": "f5acafcc0aded82b7173384f3be02bf2be31ab85b0842a15828c29e40c5539d1",
      "size": 6335
    },
    "skills/memova-agent-archive/agents/openai.yaml": {
      "sha256": "080bd0c786e329c6538576681bc233bebcc0419dfa0dc34e78a7dd056bd46fec",
      "size": 463
    },
    "skills/memova-connect/SKILL.md": {
      "sha256": "8254a65d2ebe8a9e518fc0be28b0494a3b002e61d2b540812579fdf2b04ac19b",
      "size": 3181
    },
    "skills/memova-connect/agents/openai.yaml": {
      "sha256": "7265de61625ee0b57733e64ab4807ac422a15bfd96a35b609f012c666cba3d71",
      "size": 248
    },
    "skills/memova-explicit-import/SKILL.md": {
      "sha256": "a9846d3d749b53a822c2b955cb75974dee16bfea84c33be3e2aba4170b6a318e",
      "size": 7700
    },
    "skills/memova-explicit-import/agents/openai.yaml": {
      "sha256": "eb925da8a56261a8efb3869710fdba83fd4641da830acb3544c2c17145c4c115",
      "size": 483
    },
    "skills/memova-explicit-import/scripts/preview_selected_import.py": {
      "sha256": "f4f01224c0b245dcb7ec5021714162293c758b8a1b7ec7d468ac1b197d9d06fb",
      "size": 7909
    },
    "skills/memova-knowledge/SKILL.md": {
      "sha256": "08da7d9d3589124da1d35524587ef2080c2455d191bb86ee4ed528695bcdf083",
      "size": 5445
    },
    "skills/memova-knowledge/agents/openai.yaml": {
      "sha256": "5b2b81d6559f0d588fb352b303082607953d0cac2b2bc093210862253c68998a",
      "size": 496
    },
    "skills/memova-menu/SKILL.md": {
      "sha256": "88e9ce6c3a261ae5e6f633228e6f7188d2c7884abf15efc8941f417d1d23fc5e",
      "size": 8933
    },
    "skills/memova-menu/agents/openai.yaml": {
      "sha256": "a3543b03bd48af221f0f0354f37fc32d5062bbf38c34993d8dad4cf3be05b1ee",
      "size": 204
    },
    "skills/memova-personal-manual/SKILL.md": {
      "sha256": "5e2ee8b0ae48ac8a469dc61de899e515e0356f48115cd7fa6371cb7a6d2f9b7d",
      "size": 11321
    },
    "skills/memova-personal-manual/agents/openai.yaml": {
      "sha256": "409d332354b3bfbbd1a30e2262ad44cb393774360e067d359c196ccd4dbf7d9a",
      "size": 536
    },
    "skills/memova-personal-manual/references/local-artifacts.md": {
      "sha256": "b7aa91d7a130338792a57cbe2937aea23fe6c94f7389dee3c7bd01035854dfa6",
      "size": 1914
    },
    "skills/memova-personal-manual/scripts/prepare_personal_manual.py": {
      "sha256": "51cd409550a765c45b0fa3e78c9eb7309d308c769ac5aa83443f46616644bcc8",
      "size": 19703
    },
    "skills/memova-resource-access/SKILL.md": {
      "sha256": "360b19832d8891f20b05a0ec0d6eca849b4afb80915909edc7596fc335b932b3",
      "size": 13511
    },
    "skills/memova-resource-access/agents/openai.yaml": {
      "sha256": "8f089f456a2cf60ce08dd3f006f8c835255c1b6b7f2f07af54aedc7c75c42d08",
      "size": 580
    },
    "skills/memova-vault-diagnose/SKILL.md": {
      "sha256": "b4c38e582baeb21e69ec8b04d5efa690739989a38c82468d1d3ad19465203a34",
      "size": 4809
    },
    "skills/memova-vault-diagnose/agents/openai.yaml": {
      "sha256": "cc32fa628fb4f5c3c91f750dcf72bfdf4704fda2efb33dd1281f11b5ecaf49b6",
      "size": 469
    },
    "skills/memova-vault-setup/SKILL.md": {
      "sha256": "3c4410dac28396080939f749581e42a7ce67fa704975f7d4b19aa8eee31d4537",
      "size": 12497
    },
    "skills/memova-vault-setup/agents/openai.yaml": {
      "sha256": "0c1cbe0d82408b2b2c885f90b70d0a85bcb34ec0ac20a17386c5220af5189841",
      "size": 475
    },
    "skills/memova-vault-setup/scripts/create_memova_vault.py": {
      "sha256": "3272b0f7f648e402f5035e5f10b75ca18fd143c86031fa470bd162a17e054c22",
      "size": 4151
    },
    "skills/memova-vault-setup/scripts/diagnose_memova_vault.py": {
      "sha256": "b4d6501f56f865473cda4aa7ca188735338011f7c962795bd0b4145a1e8e95a8",
      "size": 8060
    },
    "skills/memova-vault-setup/scripts/find_vault_locations.py": {
      "sha256": "850c56ae9b5aa729964d2eb7e22a0ff7d37fd8d2fe89e0e6bc6d469caa6c5bd6",
      "size": 1965
    },
    "skills/memova-vault-setup/scripts/inspect_vault.py": {
      "sha256": "7515a4d226c3a8acecfc662fb573e64a136f60aedb2460f6ed8afef212abee18",
      "size": 748
    },
    "skills/memova-vault-setup/scripts/memova_vault_lib.py": {
      "sha256": "0d7977b3485222bbebf7cf6520c01b0c55712679a7939c30689b729bcbe23bef",
      "size": 97146
    },
    "skills/memova-vault-setup/scripts/setup_fixture_harness.py": {
      "sha256": "605d8d19a51b551611bca2cdd612651e27a490a66d9f9b5958d39d876e27e3a6",
      "size": 50807
    },
    "skills/memova-vault-setup/scripts/validate_memova_vault.py": {
      "sha256": "0c0632e1a8e192fae3b9802501cc298a2c3887ce63a552a469e32b83d6737269",
      "size": 2681
    },
    "skills/memova-workflow/SKILL.md": {
      "sha256": "7b98b4534cdec921a6b82f422f445a46d3a4f78cbb4feeabdfee1b3b924cb264",
      "size": 10156
    },
    "skills/memova-workflow/agents/openai.yaml": {
      "sha256": "ade51e2a4a35f1ce6b1d05bda9d183ce58a10aedeab183bedc6382fbe5b47bbd",
      "size": 445
    }
  }
}

SHA-256 of public snapshot: c9fc0f7767b2429980a672046ee55277b419ec8f437b75a2051fe27afb04c9d8