导出源代码的项目需要可移植性测试
导出源代码的项目仍可能依赖其 AI 构建器。签约前请测试运行时调用、SDK、身份、数据、CI 和托管。

导出源代码只能证明你收到了文件,不能证明原始 AI 应用构建器消失后,项目仍能构建、启动、验证用户身份、读取生产数据或完成部署。应把可移植性视为验收测试,而不是销售合同里的一个勾选项。
我接手过足够多的生成式应用,因此见到一个干净的代码仓库也不会立刻放心。代价高昂的故障往往藏在显眼的应用代码之外:向供应商服务发出的运行时请求、登记在他人租户中的认证回调、从未进入版本控制的数据库策略,或只存在于托管控制台中的部署设置。只有当你的团队能在自己控制的账户下,凭导出内容和有文档记录的外部服务复现其正常行为,项目才算可移植。
导出源代码的项目仍可能依赖构建器
只有当每项必要的构建时和运行时依赖都可用、有文档记录、可转移,并获准在构建器之外使用时,导出源代码的项目才能独立运行。这个定义比「代码仓库能编译」严格得多。它涵盖从一台空白机器到正常生产发布的全过程,包括身份、数据、定时任务、密钥、网络规则和恢复。
三种不同的主张经常被混为一谈。获得源代码意味着你可以查看文件。构建独立性意味着你无需调用构建器就能产出构件。运行时独立性意味着这些构件不依赖构建器也能持续处理真实请求。供应商可能满足第一项,却无法满足后两项。
这一区别会直接影响合同。如果合同承诺「源代码导出」,你可能会收到一个 React 目录、包清单和 README,却仍需要专有 SDK 或托管网关。应要求可操作的结果:获授权的工程师必须能使用客户自有账户,在干净环境中构建并运行已验收的版本。
测试前先界定边界。使用托管服务并不必然意味着可移植性失败。大多数严肃的应用都依赖云服务、支付处理商、邮件提供商或身份服务。关键在于你是否明知并选择了这些依赖,以及能否通过自己的协议迁移或替换它们。无法单独签约的隐藏供应商服务,与位于你云账户中的、已有文档的 PostgreSQL 数据库截然不同。
为每个外部组件建立依赖登记表,包含四个字段:所有者、用途、替换路径和失效行为。「所有者」指合法账户持有人,不是谁知道密码。「替换路径」可以是迁移流程、你能重新实现的接口,或明确决定继续保留该服务。「失效行为」记录服务不可用时用户会看到什么。如果卖方无法填清这些字段,说明导出内容的说明仍不足以衡量风险价格。
最好的第一项测试很朴素:断开对构建器账户的访问,然后尝试运行应用。在预发布副本中撤销其令牌,在网络边界屏蔽已知域名,观察哪些地方失败。不要一开始就逐个阅读文件。运行时证据能发现代码审查遗漏的依赖,包括注入的配置和编译后包发出的调用。
在真实工作流运行时追踪应用
当你在代表性工作流中观察 DNS、出站连接、浏览器请求和后台任务时,运行时回调就会显现。能加载首页几乎说明不了什么。请操作登录、密码找回、文件上传、搜索、计费状态变更、邮件投递、定时任务、管理操作,以及产品实际出售的任何 AI 功能。
在全新的预发布网络中运行应用,并记录所有出站流量。只允许它访问依赖登记表中的目标地址。如果环境允许,先对未列出的流量采取拒绝策略。每个被阻止的请求都应回答一个问题:它是必需的、可选遥测、更新检查,还是未记录的控制平面调用?
浏览器开发者工具很重要,因为有些依赖永远不会经过你的服务器。清除存储并使用新会话后,检查 Network 面板。查看请求主机、失败的预检请求、WebSocket 连接、加载的脚本和重定向。即使服务器代码仓库看起来完整,前端也可能直接调用构建器 API。服务工作线程还可能保留旧行为,所以重复测试前应先注销它们。
在类 Unix 的源代码树中,这个搜索命令可提供一份有用的初始清单:
grep -R -n -E 'https?:|wss?:|fetch[(]|axios|WebSocket|grpc|callback|webhook' .
预期输出格式类似 path/to/file:line:matching text。应将生成的锁文件与应用代码分开审查,因为包元数据中的域名不能证明存在运行时调用。反过来,搜索结果干净也不能证明独立性:环境变量可以拼接主机名,DNS 别名可以隐藏它们,二进制依赖也能自行发出请求。
分别搜索供应商术语、SDK 导入和环境变量前缀。然后检查锁文件,确认包是从公共注册表还是私有供应商注册表解析而来。缓存成功会在这里误导你。删除隔离测试环境中的语言包缓存,只使用有文档记录的注册表凭据重新构建。
追踪后台行为的时间要足够跨过一次调度周期。Web 进程可能看似健康,队列消费者却失败、定时报表停止、Webhook 重试不断堆积。若等待正常调度会拖慢测试,就手动触发任务。记录每个出站集成的目标地址、请求方法、认证类型、响应类别、重试规则及对用户可见的影响。
不要在未测试失效情况前接受「那个回调只是遥测」的说法。屏蔽它后重复工作流。可选遥测应迅速超时,或在不影响用户操作的情况下失败。我曾见过日志调用放在请求事务中,结果一次无害的分析服务故障变成保存失败。标签不能决定风险,代码路径才能决定。
专有 SDK 需要移除或许可路径
专有 SDK 只有在你能获取它、针对它构建、合法运行它,并能在业务可承受的期限内替换它时才可以接受。导出内容中含有其包装器源代码,并不授予你对其 SDK、协议、托管端点或背后模型的权利。
从清单和源代码导入两方面盘点依赖。JavaScript 要检查 package.json 及其锁文件。Go 要检查 go.mod 和校验和。Flutter 要检查 pubspec.yaml 及其锁文件。注意从 Git 仓库、私有注册表、本地路径或归档文件获取的包。这些地方常被用来隐藏归构建器所有的组件。
对每个可疑包,回答四个具体问题:
- 新建的、由客户拥有的构建代理能否下载准确版本?
- 构建器合同结束后,许可证是否允许生产使用?
- 该包是否调用客户可直接签约的服务?
- 接口是否足够小以便替换,并且是否经过测试?
使用在客户自有组织中创建的凭据进行冷构建。不要把开发者完整的配置目录复制到测试机器上。那会带入缓存包、隐含的注册表设置和个人令牌,测试也就失去意义。正确的构建流程从有文档记录的工具链版本开始,并逐一声明每项额外凭据。
如果工具链支持,可以生成软件物料清单,但不要把它当成可移植性结论。SBOM 会列出组件,却很少告诉你谁控制远程账户,或某个包是否会主动联网。用它核对代码仓库声明的内容与构建产物实际包含的内容。
如果专有客户端位于一个范围狭窄的适配器后面,现在就为适配器编写合同测试。输入已知请求,断言标准化响应,并在网络端点被屏蔽时运行相同测试。故障应清晰且边界明确。如果专有调用散布在视图组件、路由处理程序和数据模型中,应在签约前为重构定价。问题规模取决于调用点数量和语义耦合,而不是 SDK 的代码行数。
团队常会建议在购买前替换所有专有依赖。听起来很稳妥,却可能在买方本来就打算保留的服务上浪费数周。更好的规则是移除不可用或无法签约的依赖,隔离你接受的依赖,并为其余依赖附上迁移成本。可移植性意味着掌控选择,而不是应用完全没有外部服务。
认证的范围不止源代码树
只有当客户控制身份租户、重定向注册、签名密钥、用户标识符、邮件模板和恢复流程时,认证才能顺利迁移。应用代码通常只记录了这套系统的一小部分。
先把登录路径画成实际跳转。浏览器访问应用,应用重定向到身份提供商,提供商返回到已注册的回调地址,后端交换或验证凭据。记录每一跳的所有者和配置位置。如果有任何控制台只能通过构建器的组织访问,就应在验收前要求转移或替换。
托管认证会产生一个特别棘手的数据问题。应用的用户表可能存的是提供商专用的主体标识,而非邮箱地址或内部持久 ID。如果新的身份租户会签发不同主体标识,导出数据行也没有帮助。测试账户匹配、重复账户处理、密码用户、社交登录用户、多因素认证登记、被锁账户,以及更换邮箱地址的用户。
OpenID Connect 将 sub 声明定义为在签发者范围内局部唯一且永不重新分配的标识符。签发者很重要。把 sub 单独视为全球可移植的标识,可能在更换租户后关联到错误的应用记录。应将签发者与主体一起存储和比较,再设计明确的迁移映射。
测试至少需要四个账户:普通用户、管理员、已禁用用户和启用第二认证因素的用户。在客户自有租户中迁移或重建身份配置,恢复一份预发布数据库副本,验证登录成功和拒绝访问。同时测试登出、令牌刷新、密码重置、邀请接受和会话过期。团队往往记得顺利登录的路径,却在切换后才发现恢复流程已损坏。
在代码仓库中搜索重定向 URI、客户端 ID、签发者名称、Cookie 域名、受众值和签名密钥引用。密钥不应放进代码仓库,但其名称、所有者、创建步骤、轮换步骤和所需格式应写入部署文档。示例环境文件应说明这份约定,而不包含真实值:
AUTH_ISSUER=
AUTH_CLIENT_ID=
AUTH_CLIENT_SECRET=
AUTH_CALLBACK_ORIGIN=
SESSION_SIGNING_KEY=
不要因为迁移可以「以后」进行,就接受共享构建器租户作为永久安排。身份迁移会影响每个活跃用户和每项授权假设。要么在签约前转移控制权,要么将替换作为交易中已定价、经过测试的条件。
数据库可移植性还包括行为和运维
当架构、扩展、行级策略、触发器、对象存储、队列、备份和连接规则存在于数据库外部时,数据库转储并不够。数据库可移植性意味着你能恢复数据,也能复现保护和改变这些数据的行为。
从一个空的、由客户拥有的 PostgreSQL 实例开始,使用文档注明的主版本。按顺序应用代码仓库中的迁移脚本。如果项目没有迁移脚本,必须导入供应商创建的架构转储,应将其记录为缺陷。转储可能记录今天的状态,却无法说明下一次发布如何安全地改变这个状态。
将恢复后的架构与生产或预发布环境比较。检查表、列、类型、约束、索引、序列、视图、函数、触发器、已启用的扩展、角色、授权和行级安全策略。许多迁移工具会遗漏角色和提供商级别设置。应用可能通过基本读取测试,但管理任务会因恢复后的角色没有序列或函数权限而失败。
然后通过受控的往返流程验证数据路径:
- 通过公开的应用工作流创建一条记录。
- 在预期允许共享时,由第二个获授权用户读取它。
- 确认未授权用户无法读取或更改它。
- 通过应用更新并删除它。
- 将数据库恢复到另一套干净实例中,再次读取。
这个顺序会同时测试应用代码、授权策略、生成值和可恢复性。直接查看 SQL 行数无法覆盖这些行为。
当数据行指向上传文件时,应把对象存储视为数据库边界的一部分。导出存储桶、对象元数据、访问规则、生命周期规则和 URL 生成设置。如果恢复后的数据库满是对象键,而底层文件仍留在构建器所有的存储桶中,它就毫无用处。搜索索引和向量存储也同样如此:决定是迁移还是重建,并证明重建流程有效。
不要仅凭一份很小的转储判断成败。使用与预发布规模相当的副本,其中应有长文本、空值、非 ASCII 字符、大对象、夏令时变更前后的时间戳和有代表性的关系。你不需要凭空编造基准测试,需要的是证据,证明转移能在允许的停机时间内完成,且应用在之后仍能正常运行。
备份声明必须通过恢复来验证。明确谁安排备份、备份副本存在哪里、谁能解密、保留策略如何运作,以及如何发现备份失败。按照书面说明,在隔离账户中恢复一次。如果只有构建器能点击恢复按钮,你拥有的是服务功能,而不是独立的恢复计划。
缺失的 CI 流水线意味着缺失的产品知识
导出的代码仓库若没有可复现的持续集成,买方就得重新摸索工具版本、构建顺序、测试、构件打包、数据库迁移时机和发布门槛。即使卖方的内部流水线无法原样转移,这些知识仍是交付物的一部分。
寻找流水线定义、容器构建文件、工具版本文件、测试命令、代码检查规则、迁移命令和基础设施定义。然后将其与真实部署日志比较。文档往往描述简单的 Web 构建,而托管平台可能悄悄生成配置、注入服务器组件、构建移动端包,或运行数据库迁移。
在客户自有 CI 账户中重建最小流水线。它应检出固定版本、安装声明的工具链、获取依赖、运行测试、产出不可变构件,并记录构件标识。测试期间部署可以仍由人工完成,但进入预发布环境的构件必须由该流水线产出。
简洁的验收日志可以采用以下格式:
revision: 4f2c9ab
toolchain: declared versions loaded
dependencies: cold install passed
tests: unit and integration passed
artifacts: web, server, mobile
migrations: dry run passed
staging: health and workflow checks passed
具体值会不同,但每一行都需要机器输出或关联的内部记录,不能依靠某个人的记忆。将日志与验收证据一同保存。
如果不需要卖方的秘密部署机制,就不必要求它。应要求足够的说明和配置,以便复现结果。只要执行相同的必要阶段,且不削弱发布控制,可移植流水线可以使用不同的 CI 产品。
移动应用还涉及签名资产、包标识符、应用商店账户和推送通知凭据。这些很容易被忽略,因为源代码构建可在模拟器中运行而不需要它们。确认客户拥有分发账户,并记录证书轮换方式。对于服务器和 Web 应用,应将域名验证、TLS 证书签发、DNS 变更和缓存失效纳入发布演练。
流水线测试应以一次变更结束,而不是重新构建所提供的提交。做一处无害且可见的编辑,添加可回滚的数据库迁移,构建并部署到预发布环境,验证后执行回滚。这能发现那些曾被一次性提交、如今却无法重新生成的构件。
除语言工具链外,还要固定构建所用的操作系统包。原生模块可能依赖构建器镜像中恰好存在的库。新运行器可能在应用测试开始前失败,或者更糟,产出行为不同的构件。在容器定义或等效的机器可读构建描述中记录包名和版本。
CI 日志中不要出现密钥,同时要证明流水线能从客户控制的存储中获取它们。测试应创建短期预发布凭据,通过文档规定的机制注入,并在不修改源代码的情况下轮换它。如果某个密钥必须由支持人员粘贴进供应商控制台,应把这项依赖记录下来,而不是藏在设置说明里。
洁净环境部署会暴露托管假设
当一个不熟悉构建器的团队仅凭导出内容、已声明服务和书面说明,就能在客户自有环境中启动系统时,洁净环境部署便证明了可移植性。应在合同验收前进行,并设定时间限制和问题日志。
选择与预期运营模式相符的环境。从托管平台迁往裸虚拟机,会增加无关工作,也可能让一个可移植项目看似损坏。匹配容器、PostgreSQL、对象存储、定时任务、密钥和负载均衡等所需基础能力,但不要重建未记录的供应商魔法。
检查应用是否假定本地磁盘可写、端口固定、会话粘滞、代理标头可信、区域名称固定、主机名会被注入,或依赖平台专用环境变量。十二要素应用建议将配置存入环境,并把后端服务视为附加资源。这些理念仍然有用,但仅有环境变量不足以说明所有权、格式或创建方式。每个变量都应配有运维记录。
健康检查需要直接测试。如果进程在迁移完成前或必要依赖连接前就返回成功,可能会在编排器后进入重启循环。在托管系统支持时,应区分存活检查和就绪检查。逐一停止数据库、对象存储和队列,然后观察状态码、日志、重试行为,以及服务恢复后的恢复情况。
确认应用如何处理多实例。内存中的会话、本地上传目录和进程本地任务锁,在一个托管实例上可正常工作,扩容后就会失败。启动两个实例,让同一用户的请求经过二者,并运行并发任务工作器。检查会话是否保持、文件是否仍可访问,以及定时任务是否会重复执行,除非其设计本来就是幂等的。
像观察启动一样仔细观察关闭。在请求和后台任务仍活跃时发送终止信号。进程应停止接收新工作,完成或安全归还已领取的任务,关闭连接,并在主机宽限期内退出。托管构建器可能用你的新主机不具备的长超时或重试机制,掩盖了突然关闭的问题。
日志和指标也带有托管假设。确认应用将结构化事件写入有文档记录的目标位置,在需要时移除密钥和个人数据,并提供足够的信息来诊断失败工作流。只有当标准输出或其他客户控制的接收端保留了必要证据时,专有仪表盘才是可选项。
区域和数据所在地声明需要配置证据。记录应用、数据库、备份、日志和对象存储的运行地点,以及哪些外部服务会接收数据。Web 进程的区域选择器并不能保证数据留在该国,如果认证或分析服务把数据发往其他地方。合同应明确由谁批准这些地点的变更。
Koder.ai 支持源代码导出、部署和托管、自定义域名、快照和回滚。如果你评估导出的 Koder.ai 项目能否独立运行,也应使用同样的洁净环境标准:在你计划拥有的环境中测试导出的 React、使用 PostgreSQL 的 Go 或 Flutter 组件,并记录你选择保留的任何服务。
把通过和失败条件写入合同
合同应将可移植性定义为可观察行为,列出验收环境,明确整改责任,并保留足够时间在最终付款或被锁定前修复问题。模糊的所有权措辞无法挽救一个无人能部署的应用。
不要只依赖标题为「源代码」的一段文字,应附上验收矩阵。每一行应列明能力、测试流程、预期结果、证据、责任方和严重程度。覆盖冷构建、运行时网络调用、身份转移、数据库恢复、文件存储、后台任务、CI、洁净部署、监控、备份恢复、小型变更和回滚。
使用第三方可以观察的通过标准。「没有关键专有依赖」容易引发争议。「当构建器所有的凭据被撤销且构建器域名被屏蔽时,预发布应用完成 A 至 F 工作流」则可以测试。按名称和账户所有者定义允许的依赖,避免团队把获批的托管服务误判为失败。
要求在固定版本上交付源代码和运维材料:锁文件、迁移脚本、构建定义、可获得时的基础设施配置、环境变量目录、依赖登记表、数据导出、身份迁移计划、运行手册、许可证声明,以及归客户所有的签名或分发资产。明确记录排除项。沉默不应视为接受。
按业务影响设定严重程度。缺失一条可选分析事件,与登录中断并不相等。一个有用的方案会区分:阻止构建或核心工作流的阻断性问题,移除重要能力或恢复路径的重大缺陷,以及有已记录变通方案的轻微缺陷。将验收和整改日期与这些等级关联,不必杜撰通用时间表。
同时定义测试数据和测试操作人员。卖方有时会用空数据库和可绕过常规授权的管理员账户来演示可移植性。应要求由客户人员按文档流程,使用有代表性的用户、角色、文件和后台任务进行测试。密钥可以是合成的,但关系和边缘情况应保持真实。
成本也应纳入证据包。记录运行导出版本所需的单独计费服务,以及卖方指出的最低套餐、数据出口费用或私有注册表订阅。测试不必预测未来每一笔账单,但必须避免所谓独立导出在签约后才暴露出不可避免的供应商合同。
对于无法立即转移的服务,应写入协作义务。卖方可能需要轮换密钥、批准身份导出、迁移域名,或提供最终数据快照。请写明行动和负责人。生产环境宕机时,「合理协助」很难执行。
保留在整改后和最终导出后重复测试的权利。生成项目变化很快,针对上个月版本验证过的修复,无法说明昨天新增的依赖。请在验收记录中固定受测提交和构件哈希。
不要让托管条款替代这些工作。托管能在触发事件后交付文件,但没有最新构建说明、凭据所有权和经过测试的恢复路径,这些文件可能来得太晚,帮不上忙。双方还能协作时,就必须实现运维独立性。
当第二个团队无需原始构建器的特权协助,就能构建、运行、修改、部署并恢复已验收版本时再签约。否则,你拥有的只是源代码,以及一个尚未解决的迁移项目,合同价格应反映这项工作。
常见问题
导出的源代码可以脱离 AI 应用构建器运行吗?
有时可以,但仅凭代码仓库无法证明这一点。撤销构建器凭据后进行冷构建和洁净部署,并在记录出站流量的同时执行真实工作流。
源代码访问权与运行时独立性有什么区别?
源代码访问权让你能够查看和修改文件。运行时独立性意味着,正常运行的应用无需调用、凭据或基础设施只能由原始构建器控制,仍能为用户提供服务。
如何找出隐藏的应用构建器回调?
在源代码和清单文件中搜索域名、SDK、回调、WebSocket 和环境变量,然后在预发布环境观察浏览器和服务器流量。屏蔽未列出的目标地址,比相信「遥测」或「分析」这类名称更可靠。
使用托管式认证会妨碍可移植性吗?
不会,只要你的组织控制身份租户,并能迁移用户、重定向注册、签名密钥和恢复流程。没有经过验证的迁移路径,共用构建器租户就是严重依赖。
PostgreSQL 转储足以迁移数据库吗?
通常不够。你还需要迁移脚本、角色、授权、扩展、策略、触发器、对象文件、备份流程,以及证明恢复后授权和未授权工作流仍正确运行的证据。
源代码导出除了应用文件还应包含什么?
除应用文件外,还应包含锁文件、迁移脚本、构建定义、环境变量目录、依赖和许可证记录、身份与数据迁移计划以及运维手册。移动项目还需要由客户控制的签名和分发资产。
我能在购买项目前测试可移植性吗?
应把它纳入验收。使用干净且归客户所有的环境,撤销对构建器的访问,构建固定版本,部署、修改、恢复数据,并测试回滚。
专有 SDK 一定会让交易无法进行吗?
不一定。只要你能独立获取并获得许可,能直接签约所需服务,能隔离其接口,并能承担替换计划的成本,它们就可以接受。
为什么导出的项目需要 CI 配置?
CI 记录了从一个版本到经过测试的构件之间可复现的路径。没有它,工具版本、构建顺序、生成文件、迁移时机和发布检查都会成为未记录的产品知识。
怎样的合同措辞能证明导出内容具有可移植性?
请定义可观察的测试和预期结果,而不是只承诺交付源代码。要求核心工作流在客户自有环境中通过,同时撤销构建器凭据并屏蔽构建器目标地址。